&" (match-string 1)) t t)))
-
(provide 'org-entities)
;; Local variables:
diff --git a/lisp/org/org-eshell.el b/lisp/org/org-eshell.el
index 9eddd3fcf4e..34cc4ffbb8d 100644
--- a/lisp/org/org-eshell.el
+++ b/lisp/org/org-eshell.el
@@ -1,4 +1,4 @@
-;;; org-eshell.el - Support for links to working directories in eshell
+;;; org-eshell.el - Support for Links to Working Directories in Eshell -*- lexical-binding: t; -*-
;; Copyright (C) 2011-2017 Free Software Foundation, Inc.
@@ -27,8 +27,9 @@
(require 'eshell)
(require 'esh-mode)
-(org-add-link-type "eshell" 'org-eshell-open)
-(add-hook 'org-store-link-functions 'org-eshell-store-link)
+(org-link-set-parameters "eshell"
+ :follow #'org-eshell-open
+ :store #'org-eshell-store-link)
(defun org-eshell-open (link)
"Switch to am eshell buffer and execute a command line.
@@ -43,7 +44,7 @@
(eshell-buffer-name (car buffer-and-command))
(command (cadr buffer-and-command)))
(if (get-buffer eshell-buffer-name)
- (org-pop-to-buffer-same-window eshell-buffer-name)
+ (pop-to-buffer-same-window eshell-buffer-name)
(eshell))
(goto-char (point-max))
(eshell-kill-input)
diff --git a/lisp/org/org-eww.el b/lisp/org/org-eww.el
new file mode 100644
index 00000000000..c14ae115afb
--- /dev/null
+++ b/lisp/org/org-eww.el
@@ -0,0 +1,175 @@
+;;; org-eww.el --- Store url and kill from Eww mode for Org -*- lexical-binding: t -*-
+
+;; Copyright (C) 2014-2017 Free Software Foundation, Inc.
+
+;; Author: Marco Wahl a
+;; Keywords: link, eww
+;; Homepage: http://orgmode.org
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see .
+
+
+;;; Commentary:
+
+;; When this module is active `org-store-link' (often on key C-c l) in
+;; a eww buffer stores a link to the current url of the eww buffer.
+
+;; In an eww buffer function `org-eww-copy-for-org-mode' kills either
+;; a region or the whole buffer if no region is set and transforms the
+;; text on the fly so that it can be pasted into an Org buffer with
+;; hot links.
+
+;; C-c C-x C-w (and also C-c C-x M-w) trigger
+;; `org-eww-copy-for-org-mode'.
+
+;; Hint: A lot of code of this module comes from module org-w3m which
+;; has been written by Andy Steward based on the idea of Richard
+;; Riley. Thanks!
+
+;; Potential: Since the code for w3m and eww is so similar one could
+;; try to refactor.
+
+
+;;; Code:
+(require 'org)
+(require 'cl-lib)
+
+(defvar eww-current-title)
+(defvar eww-current-url)
+(defvar eww-data)
+(defvar eww-mode-map)
+
+(declare-function eww-current-url "eww")
+
+
+;; Store Org-link in eww-mode buffer
+(org-link-set-parameters "eww" :follow #'eww :store #'org-eww-store-link)
+(defun org-eww-store-link ()
+ "Store a link to the url of a Eww buffer."
+ (when (eq major-mode 'eww-mode)
+ (org-store-link-props
+ :type "eww"
+ :link (if (< emacs-major-version 25)
+ eww-current-url
+ (eww-current-url))
+ :url (url-view-url t)
+ :description (if (< emacs-major-version 25)
+ (or eww-current-title eww-current-url)
+ (or (plist-get eww-data :title)
+ (eww-current-url))))))
+
+
+;; Some auxiliary functions concerning links in eww buffers
+(defun org-eww-goto-next-url-property-change ()
+ "Move to the start of next link if exists.
+Otherwise point is not moved. Return point."
+ (goto-char
+ (or (next-single-property-change (point) 'shr-url)
+ (point))))
+
+(defun org-eww-has-further-url-property-change-p ()
+ "Non-nil if there is a next url property change."
+ (save-excursion
+ (not (eq (point) (org-eww-goto-next-url-property-change)))))
+
+(defun org-eww-url-below-point ()
+ "Return the url below point if there is an url; otherwise, return nil."
+ (get-text-property (point) 'shr-url))
+
+
+(defun org-eww-copy-for-org-mode ()
+ "Copy current buffer content or active region with `org-mode' style links.
+This will encode `link-title' and `link-location' with
+`org-make-link-string', and insert the transformed test into the kill ring,
+so that it can be yanked into an Org mode buffer with links working correctly.
+
+Further lines starting with a star get quoted with a comma to keep
+the structure of the Org file."
+ (interactive)
+ (let* ((regionp (org-region-active-p))
+ (transform-start (point-min))
+ (transform-end (point-max))
+ return-content
+ link-location link-title
+ temp-position out-bound)
+ (when regionp
+ (setq transform-start (region-beginning))
+ (setq transform-end (region-end))
+ ;; Deactivate mark if current mark is activate.
+ (when (fboundp 'deactivate-mark) (deactivate-mark)))
+ (message "Transforming links...")
+ (save-excursion
+ (goto-char transform-start)
+ (while (and (not out-bound) ; still inside region to copy
+ (org-eww-has-further-url-property-change-p)) ; there is a next link
+ ;; Store current point before jump next anchor.
+ (setq temp-position (point))
+ ;; Move to next anchor when current point is not at anchor.
+ (or (org-eww-url-below-point)
+ (org-eww-goto-next-url-property-change))
+ (cl-assert
+ (org-eww-url-below-point) t
+ "program logic error: point must have an url below but it hasn't")
+ (if (<= (point) transform-end) ; if point is inside transform bound
+ (progn
+ ;; Get content between two links.
+ (when (< temp-position (point))
+ (setq return-content (concat return-content
+ (buffer-substring
+ temp-position (point)))))
+ ;; Get link location at current point.
+ (setq link-location (org-eww-url-below-point))
+ ;; Get link title at current point.
+ (setq link-title
+ (buffer-substring
+ (point)
+ (org-eww-goto-next-url-property-change)))
+ ;; concat `org-mode' style url to `return-content'.
+ (setq return-content
+ (concat return-content
+ (if (stringp link-location)
+ ;; hint: link-location is different for form-elements.
+ (org-make-link-string link-location link-title)
+ link-title))))
+ (goto-char temp-position) ; reset point before jump next anchor
+ (setq out-bound t) ; for break out `while' loop
+ ))
+ ;; Add the rest until end of the region to be copied.
+ (when (< (point) transform-end)
+ (setq return-content
+ (concat return-content
+ (buffer-substring (point) transform-end))))
+ ;; Quote lines starting with *.
+ (org-kill-new (replace-regexp-in-string "^\\*" ",*" return-content))
+ (message "Transforming links...done, use C-y to insert text into Org mode file"))))
+
+
+;; Additional keys for eww-mode
+
+(defun org-eww-extend-eww-keymap ()
+ (define-key eww-mode-map "\C-c\C-x\M-w" 'org-eww-copy-for-org-mode)
+ (define-key eww-mode-map "\C-c\C-x\C-w" 'org-eww-copy-for-org-mode))
+
+(when (and (boundp 'eww-mode-map)
+ (keymapp eww-mode-map)) ; eww is already up.
+ (org-eww-extend-eww-keymap))
+
+(add-hook 'eww-mode-hook #'org-eww-extend-eww-keymap)
+
+
+(provide 'org-eww)
+
+;;; org-eww.el ends here
diff --git a/lisp/org/org-faces.el b/lisp/org/org-faces.el
index c340aca73a5..cd43d37178b 100644
--- a/lisp/org/org-faces.el
+++ b/lisp/org/org-faces.el
@@ -1,4 +1,4 @@
-;;; org-faces.el --- Face definitions for Org-mode.
+;;; org-faces.el --- Face definitions -*- lexical-binding: t; -*-
;; Copyright (C) 2004-2017 Free Software Foundation, Inc.
@@ -28,32 +28,12 @@
;;; Code:
-(require 'org-macs)
-(require 'org-compat)
-
-(defun org-copy-face (old-face new-face docstring &rest attributes)
- (unless (facep new-face)
- (if (fboundp 'set-face-attribute)
- (progn
- (make-face new-face)
- (set-face-attribute new-face nil :inherit old-face)
- (apply 'set-face-attribute new-face nil attributes)
- (set-face-doc-string new-face docstring))
- (copy-face old-face new-face)
- (if (fboundp 'set-face-doc-string)
- (set-face-doc-string new-face docstring)))))
-(put 'org-copy-face 'lisp-indent-function 2)
-
-(when (featurep 'xemacs)
- (put 'mode-line 'face-alias 'modeline))
-
(defgroup org-faces nil
- "Faces in Org-mode."
+ "Faces in Org mode."
:tag "Org Faces"
:group 'org-appearance)
-(defface org-default
- (org-compatible-face 'default nil)
+(defface org-default '((t :inherit default))
"Face used for default text."
:group 'org-faces)
@@ -65,99 +45,49 @@ The foreground color of this face should be equal to the background
color of the frame."
:group 'org-faces)
-(defface org-level-1 ;; originally copied from font-lock-function-name-face
- (org-compatible-face 'outline-1
- '((((class color) (min-colors 88) (background light)) (:foreground "Blue1"))
- (((class color) (min-colors 88) (background dark)) (:foreground "LightSkyBlue"))
- (((class color) (min-colors 16) (background light)) (:foreground "Blue"))
- (((class color) (min-colors 16) (background dark)) (:foreground "LightSkyBlue"))
- (((class color) (min-colors 8)) (:foreground "blue" :bold t))
- (t (:bold t))))
+(defface org-level-1 '((t :inherit outline-1))
"Face used for level 1 headlines."
:group 'org-faces)
-(defface org-level-2 ;; originally copied from font-lock-variable-name-face
- (org-compatible-face 'outline-2
- '((((class color) (min-colors 16) (background light)) (:foreground "DarkGoldenrod"))
- (((class color) (min-colors 16) (background dark)) (:foreground "LightGoldenrod"))
- (((class color) (min-colors 8) (background light)) (:foreground "yellow"))
- (((class color) (min-colors 8) (background dark)) (:foreground "yellow" :bold t))
- (t (:bold t))))
+(defface org-level-2 '((t :inherit outline-2))
"Face used for level 2 headlines."
:group 'org-faces)
-(defface org-level-3 ;; originally copied from font-lock-keyword-face
- (org-compatible-face 'outline-3
- '((((class color) (min-colors 88) (background light)) (:foreground "Purple"))
- (((class color) (min-colors 88) (background dark)) (:foreground "Cyan1"))
- (((class color) (min-colors 16) (background light)) (:foreground "Purple"))
- (((class color) (min-colors 16) (background dark)) (:foreground "Cyan"))
- (((class color) (min-colors 8) (background light)) (:foreground "purple" :bold t))
- (((class color) (min-colors 8) (background dark)) (:foreground "cyan" :bold t))
- (t (:bold t))))
+(defface org-level-3 '((t :inherit outline-3))
"Face used for level 3 headlines."
:group 'org-faces)
-(defface org-level-4 ;; originally copied from font-lock-comment-face
- (org-compatible-face 'outline-4
- '((((class color) (min-colors 88) (background light)) (:foreground "Firebrick"))
- (((class color) (min-colors 88) (background dark)) (:foreground "chocolate1"))
- (((class color) (min-colors 16) (background light)) (:foreground "red"))
- (((class color) (min-colors 16) (background dark)) (:foreground "red1"))
- (((class color) (min-colors 8) (background light)) (:foreground "red" :bold t))
- (((class color) (min-colors 8) (background dark)) (:foreground "red" :bold t))
- (t (:bold t))))
+(defface org-level-4 '((t :inherit outline-4))
"Face used for level 4 headlines."
:group 'org-faces)
-(defface org-level-5 ;; originally copied from font-lock-type-face
- (org-compatible-face 'outline-5
- '((((class color) (min-colors 16) (background light)) (:foreground "ForestGreen"))
- (((class color) (min-colors 16) (background dark)) (:foreground "PaleGreen"))
- (((class color) (min-colors 8)) (:foreground "green"))))
+(defface org-level-5 '((t :inherit outline-5))
"Face used for level 5 headlines."
:group 'org-faces)
-(defface org-level-6 ;; originally copied from font-lock-constant-face
- (org-compatible-face 'outline-6
- '((((class color) (min-colors 16) (background light)) (:foreground "CadetBlue"))
- (((class color) (min-colors 16) (background dark)) (:foreground "Aquamarine"))
- (((class color) (min-colors 8)) (:foreground "magenta"))))
+(defface org-level-6 '((t :inherit outline-6))
"Face used for level 6 headlines."
:group 'org-faces)
-(defface org-level-7 ;; originally copied from font-lock-builtin-face
- (org-compatible-face 'outline-7
- '((((class color) (min-colors 16) (background light)) (:foreground "Orchid"))
- (((class color) (min-colors 16) (background dark)) (:foreground "LightSteelBlue"))
- (((class color) (min-colors 8)) (:foreground "blue"))))
+(defface org-level-7 '((t :inherit outline-7))
"Face used for level 7 headlines."
:group 'org-faces)
-(defface org-level-8 ;; originally copied from font-lock-string-face
- (org-compatible-face 'outline-8
- '((((class color) (min-colors 16) (background light)) (:foreground "RosyBrown"))
- (((class color) (min-colors 16) (background dark)) (:foreground "LightSalmon"))
- (((class color) (min-colors 8)) (:foreground "green"))))
+(defface org-level-8 '((t :inherit outline-8))
"Face used for level 8 headlines."
:group 'org-faces)
-(defface org-special-keyword ;; originally copied from font-lock-string-face
- (org-compatible-face 'font-lock-keyword-face
- '((((class color) (min-colors 16) (background light)) (:foreground "RosyBrown"))
- (((class color) (min-colors 16) (background dark)) (:foreground "LightSalmon"))
- (t (:italic t))))
+(defface org-special-keyword '((t :inherit font-lock-keyword-face))
"Face used for special keywords."
:group 'org-faces)
-(defface org-drawer ;; originally copied from font-lock-function-name-face
- (org-compatible-face nil
- '((((class color) (min-colors 88) (background light)) (:foreground "Blue1"))
- (((class color) (min-colors 88) (background dark)) (:foreground "LightSkyBlue"))
- (((class color) (min-colors 16) (background light)) (:foreground "Blue"))
- (((class color) (min-colors 16) (background dark)) (:foreground "LightSkyBlue"))
- (((class color) (min-colors 8)) (:foreground "blue" :bold t))
- (t (:bold t))))
+(defface org-drawer ;Copied from `font-lock-function-name-face'
+ '((((class color) (min-colors 88) (background light)) (:foreground "Blue1"))
+ (((class color) (min-colors 88) (background dark)) (:foreground "LightSkyBlue"))
+ (((class color) (min-colors 16) (background light)) (:foreground "Blue"))
+ (((class color) (min-colors 16) (background dark)) (:foreground "LightSkyBlue"))
+ (((class color) (min-colors 8)) (:foreground "blue" :bold t))
+ (t (:bold t)))
"Face used for drawers."
:group 'org-faces)
@@ -166,18 +96,17 @@ color of the frame."
:group 'org-faces)
(defface org-column
- (org-compatible-face nil
- '((((class color) (min-colors 16) (background light))
- (:background "grey90" :weight normal :slant normal :strike-through nil
- :underline nil))
- (((class color) (min-colors 16) (background dark))
- (:background "grey30" :weight normal :slant normal :strike-through nil
- :underline nil))
- (((class color) (min-colors 8))
- (:background "cyan" :foreground "black"
- :weight normal :slant normal :strike-through nil
- :underline nil))
- (t (:inverse-video t))))
+ '((((class color) (min-colors 16) (background light))
+ (:background "grey90" :weight normal :slant normal :strike-through nil
+ :underline nil))
+ (((class color) (min-colors 16) (background dark))
+ (:background "grey30" :weight normal :slant normal :strike-through nil
+ :underline nil))
+ (((class color) (min-colors 8))
+ (:background "cyan" :foreground "black"
+ :weight normal :slant normal :strike-through nil
+ :underline nil))
+ (t (:inverse-video t)))
"Face for column display of entry properties.
This is actually only part of the face definition for the text in column view.
The following faces apply, with this priority.
@@ -198,59 +127,33 @@ character (this might for example be the a TODO keyword) might still
shine through in some properties. So when your column view looks
funny, with \"random\" colors, weight, strike-through, try to explicitly
set the properties in the `org-column' face. For example, set
-:underline to nil, or the :slant to `normal'.
-
-Under XEmacs, the rules are simpler, because the XEmacs version of
-column view defines special faces for each outline level. See the file
-`org-colview-xemacs.el' in Org's contrib/ directory for details."
+:underline to nil, or the :slant to `normal'."
:group 'org-faces)
(defface org-column-title
- (org-compatible-face nil
- '((((class color) (min-colors 16) (background light))
- (:background "grey90" :underline t :weight bold))
- (((class color) (min-colors 16) (background dark))
- (:background "grey30" :underline t :weight bold))
- (((class color) (min-colors 8))
- (:background "cyan" :foreground "black" :underline t :weight bold))
- (t (:inverse-video t))))
+ '((((class color) (min-colors 16) (background light))
+ (:background "grey90" :underline t :weight bold))
+ (((class color) (min-colors 16) (background dark))
+ (:background "grey30" :underline t :weight bold))
+ (((class color) (min-colors 8))
+ (:background "cyan" :foreground "black" :underline t :weight bold))
+ (t (:inverse-video t)))
"Face for column display of entry properties."
:group 'org-faces)
-(defface org-agenda-column-dateline
- (org-compatible-face 'org-column
- '((t nil)))
+(defface org-agenda-column-dateline '((t :inherit org-column))
"Face used in agenda column view for datelines with summaries."
:group 'org-faces)
-(defface org-warning
- (org-compatible-face 'font-lock-warning-face
- '((((class color) (min-colors 16) (background light)) (:foreground "Red1" :bold t))
- (((class color) (min-colors 16) (background dark)) (:foreground "Pink" :bold t))
- (((class color) (min-colors 8) (background light)) (:foreground "red" :bold t))
- (((class color) (min-colors 8) (background dark)) (:foreground "red" :bold t))
- (t (:bold t))))
+(defface org-warning '((t :inherit font-lock-warning-face))
"Face for deadlines and TODO keywords."
:group 'org-faces)
-(defface org-archived ; similar to shadow
- (org-compatible-face 'shadow
- '((((class color grayscale) (min-colors 88) (background light))
- (:foreground "grey50"))
- (((class color grayscale) (min-colors 88) (background dark))
- (:foreground "grey70"))
- (((class color) (min-colors 8) (background light))
- (:foreground "green"))
- (((class color) (min-colors 8) (background dark))
- (:foreground "yellow"))))
+(defface org-archived '((t :inherit shadow))
"Face for headline with the ARCHIVE tag."
:group 'org-faces)
-(defface org-link
- (org-compatible-face 'link
- '((((class color) (background light)) (:foreground "Purple" :underline t))
- (((class color) (background dark)) (:foreground "Cyan" :underline t))
- (t (:underline t))))
+(defface org-link '((t :inherit link))
"Face for links."
:group 'org-faces)
@@ -283,12 +186,11 @@ column view defines special faces for each outline level. See the file
:group 'org-faces)
(defface org-date-selected
- (org-compatible-face nil
- '((((class color) (min-colors 16) (background light)) (:foreground "Red1" :inverse-video t))
- (((class color) (min-colors 16) (background dark)) (:foreground "Pink" :inverse-video t))
- (((class color) (min-colors 8) (background light)) (:foreground "red" :inverse-video t))
- (((class color) (min-colors 8) (background dark)) (:foreground "red" :inverse-video t))
- (t (:inverse-video t))))
+ '((((class color) (min-colors 16) (background light)) (:foreground "Red1" :inverse-video t))
+ (((class color) (min-colors 16) (background dark)) (:foreground "Pink" :inverse-video t))
+ (((class color) (min-colors 8) (background light)) (:foreground "red" :inverse-video t))
+ (((class color) (min-colors 8) (background dark)) (:foreground "red" :inverse-video t))
+ (t (:inverse-video t)))
"Face for highlighting the calendar day when using `org-read-date'.
Using a bold face here might cause discrepancies while displaying the
calendar."
@@ -301,43 +203,38 @@ calendar."
"Face for diary-like sexp date specifications."
:group 'org-faces)
-(defface org-tag
- '((t (:bold t)))
+(defface org-tag '((t (:bold t)))
"Default face for tags.
Note that the variable `org-tag-faces' can be used to overrule this face for
specific tags."
:group 'org-faces)
-(defface org-list-dt
- '((t (:bold t)))
+(defface org-list-dt '((t (:bold t)))
"Default face for definition terms in lists."
:group 'org-faces)
-(defface org-todo ; font-lock-warning-face
- (org-compatible-face nil
- '((((class color) (min-colors 16) (background light)) (:foreground "Red1" :bold t))
- (((class color) (min-colors 16) (background dark)) (:foreground "Pink" :bold t))
- (((class color) (min-colors 8) (background light)) (:foreground "red" :bold t))
- (((class color) (min-colors 8) (background dark)) (:foreground "red" :bold t))
- (t (:inverse-video t :bold t))))
+(defface org-todo ;Copied from `font-lock-warning-face'
+ '((((class color) (min-colors 16) (background light)) (:foreground "Red1" :bold t))
+ (((class color) (min-colors 16) (background dark)) (:foreground "Pink" :bold t))
+ (((class color) (min-colors 8) (background light)) (:foreground "red" :bold t))
+ (((class color) (min-colors 8) (background dark)) (:foreground "red" :bold t))
+ (t (:inverse-video t :bold t)))
"Face for TODO keywords."
:group 'org-faces)
-(defface org-done ;; originally copied from font-lock-type-face
- (org-compatible-face nil
- '((((class color) (min-colors 16) (background light)) (:foreground "ForestGreen" :bold t))
- (((class color) (min-colors 16) (background dark)) (:foreground "PaleGreen" :bold t))
- (((class color) (min-colors 8)) (:foreground "green"))
- (t (:bold t))))
+(defface org-done ;Copied from `font-lock-type-face'
+ '((((class color) (min-colors 16) (background light)) (:foreground "ForestGreen" :bold t))
+ (((class color) (min-colors 16) (background dark)) (:foreground "PaleGreen" :bold t))
+ (((class color) (min-colors 8)) (:foreground "green"))
+ (t (:bold t)))
"Face used for todo keywords that indicate DONE items."
:group 'org-faces)
-(defface org-agenda-done ;; originally copied from font-lock-type-face
- (org-compatible-face nil
- '((((class color) (min-colors 16) (background light)) (:foreground "ForestGreen"))
- (((class color) (min-colors 16) (background dark)) (:foreground "PaleGreen"))
- (((class color) (min-colors 8)) (:foreground "green"))
- (t (:bold nil))))
+(defface org-agenda-done ;Copied from `font-lock-type-face'
+ '((((class color) (min-colors 16) (background light)) (:foreground "ForestGreen"))
+ (((class color) (min-colors 16) (background dark)) (:foreground "PaleGreen"))
+ (((class color) (min-colors 8)) (:foreground "green"))
+ (t (:bold nil)))
"Face used in agenda, to indicate lines switched to DONE.
This face is used to de-emphasize items that where brightly colored in the
agenda because they were things to do, or overdue. The DONE state itself
@@ -346,11 +243,10 @@ is of course immediately visible, but for example a passed deadline is
of the frame, for example."
:group 'org-faces)
-(defface org-headline-done ;; originally copied from font-lock-string-face
- (org-compatible-face nil
- '((((class color) (min-colors 16) (background light)) (:foreground "RosyBrown"))
- (((class color) (min-colors 16) (background dark)) (:foreground "LightSalmon"))
- (((class color) (min-colors 8) (background light)) (:bold nil))))
+(defface org-headline-done ;Copied from `font-lock-string-face'
+ '((((class color) (min-colors 16) (background light)) (:foreground "RosyBrown"))
+ (((class color) (min-colors 16) (background dark)) (:foreground "LightSalmon"))
+ (((class color) (min-colors 8) (background light)) (:bold nil)))
"Face used to indicate that a headline is DONE.
This face is only used if `org-fontify-done-headline' is set. If applies
to the part of the headline after the DONE keyword."
@@ -388,11 +284,7 @@ determines if it is a foreground or a background color."
(string :tag "Color")
(sexp :tag "Face")))))
-(defface org-priority ;; originally copied from font-lock-string-face
- (org-compatible-face 'font-lock-keyword-face
- '((((class color) (min-colors 16) (background light)) (:foreground "RosyBrown"))
- (((class color) (min-colors 16) (background dark)) (:foreground "LightSalmon"))
- (t (:italic t))))
+(defface org-priority '((t :inherit font-lock-keyword-face))
"Face used for priority cookies."
:group 'org-faces)
@@ -421,18 +313,17 @@ determines if it is a foreground or a background color."
(setq org-tags-special-faces-re
(concat ":\\(" (mapconcat 'car value "\\|") "\\):"))))
-(defface org-checkbox
- (org-compatible-face 'bold
- '((t (:bold t))))
+(defface org-checkbox '((t :inherit bold))
"Face for checkboxes."
:group 'org-faces)
+(defface org-checkbox-statistics-todo '((t (:inherit org-todo)))
+ "Face used for unfinished checkbox statistics."
+ :group 'org-faces)
-(org-copy-face 'org-todo 'org-checkbox-statistics-todo
- "Face used for unfinished checkbox statistics.")
-
-(org-copy-face 'org-done 'org-checkbox-statistics-done
- "Face used for finished checkbox statistics.")
+(defface org-checkbox-statistics-done '((t (:inherit org-done)))
+ "Face used for finished checkbox statistics."
+ :group 'org-faces)
(defcustom org-tag-faces nil
"Faces for specific tags.
@@ -454,44 +345,32 @@ changes."
(string :tag "Foreground color")
(sexp :tag "Face")))))
-(defface org-table ;; originally copied from font-lock-function-name-face
- (org-compatible-face nil
- '((((class color) (min-colors 88) (background light)) (:foreground "Blue1"))
- (((class color) (min-colors 88) (background dark)) (:foreground "LightSkyBlue"))
- (((class color) (min-colors 16) (background light)) (:foreground "Blue"))
- (((class color) (min-colors 16) (background dark)) (:foreground "LightSkyBlue"))
- (((class color) (min-colors 8) (background light)) (:foreground "blue"))
- (((class color) (min-colors 8) (background dark)))))
+(defface org-table ;Copied from `font-lock-function-name-face'
+ '((((class color) (min-colors 88) (background light)) (:foreground "Blue1"))
+ (((class color) (min-colors 88) (background dark)) (:foreground "LightSkyBlue"))
+ (((class color) (min-colors 16) (background light)) (:foreground "Blue"))
+ (((class color) (min-colors 16) (background dark)) (:foreground "LightSkyBlue"))
+ (((class color) (min-colors 8) (background light)) (:foreground "blue"))
+ (((class color) (min-colors 8) (background dark))))
"Face used for tables."
:group 'org-faces)
(defface org-formula
- (org-compatible-face nil
- '((((class color) (min-colors 88) (background light)) (:foreground "Firebrick"))
- (((class color) (min-colors 88) (background dark)) (:foreground "chocolate1"))
- (((class color) (min-colors 8) (background light)) (:foreground "red"))
- (((class color) (min-colors 8) (background dark)) (:foreground "red"))
- (t (:bold t :italic t))))
+ '((((class color) (min-colors 88) (background light)) (:foreground "Firebrick"))
+ (((class color) (min-colors 88) (background dark)) (:foreground "chocolate1"))
+ (((class color) (min-colors 8) (background light)) (:foreground "red"))
+ (((class color) (min-colors 8) (background dark)) (:foreground "red"))
+ (t (:bold t :italic t)))
"Face for formulas."
:group 'org-faces)
-(defface org-code
- (org-compatible-face 'shadow
- '((((class color grayscale) (min-colors 88) (background light))
- (:foreground "grey50"))
- (((class color grayscale) (min-colors 88) (background dark))
- (:foreground "grey70"))
- (((class color) (min-colors 8) (background light))
- (:foreground "green"))
- (((class color) (min-colors 8) (background dark))
- (:foreground "yellow"))))
+(defface org-code '((t :inherit shadow))
"Face for fixed-width text like code snippets."
:group 'org-faces
:version "22.1")
-(defface org-meta-line
- (org-compatible-face 'font-lock-comment-face nil)
- "Face for meta lines startin with \"#+\"."
+(defface org-meta-line '((t :inherit font-lock-comment-face))
+ "Face for meta lines starting with \"#+\"."
:group 'org-faces
:version "22.1")
@@ -510,60 +389,37 @@ changes."
follows a #+DATE:, #+AUTHOR: or #+EMAIL: keyword."
:group 'org-faces)
-(defface org-document-info-keyword
- (org-compatible-face 'shadow
- '((((class color grayscale) (min-colors 88) (background light))
- (:foreground "grey50"))
- (((class color grayscale) (min-colors 88) (background dark))
- (:foreground "grey70"))
- (((class color) (min-colors 8) (background light))
- (:foreground "green"))
- (((class color) (min-colors 8) (background dark))
- (:foreground "yellow"))))
+(defface org-document-info-keyword '((t :inherit shadow))
"Face for #+TITLE:, #+AUTHOR:, #+EMAIL: and #+DATE: keywords."
:group 'org-faces)
-(defface org-block
- (org-compatible-face 'shadow
- '((((class color grayscale) (min-colors 88) (background light))
- (:foreground "grey50"))
- (((class color grayscale) (min-colors 88) (background dark))
- (:foreground "grey70"))
- (((class color) (min-colors 8) (background light))
- (:foreground "green"))
- (((class color) (min-colors 8) (background dark))
- (:foreground "yellow"))))
- "Face text in #+begin ... #+end blocks."
+(defface org-block '((t :inherit shadow))
+ "Face text in #+begin ... #+end blocks.
+For source-blocks `org-src-block-faces' takes precedence.
+See also `org-fontify-quote-and-verse-blocks'."
:group 'org-faces
- :version "22.1")
+ :version "26.1")
-(defface org-block-background '((t ()))
- "Face used for the source block background.")
-
-(org-copy-face 'org-meta-line 'org-block-begin-line
- "Face used for the line delimiting the begin of source blocks.")
-
-(org-copy-face 'org-meta-line 'org-block-end-line
- "Face used for the line delimiting the end of source blocks.")
-
-(defface org-verbatim
- (org-compatible-face 'shadow
- '((((class color grayscale) (min-colors 88) (background light))
- (:foreground "grey50" :underline t))
- (((class color grayscale) (min-colors 88) (background dark))
- (:foreground "grey70" :underline t))
- (((class color) (min-colors 8) (background light))
- (:foreground "green" :underline t))
- (((class color) (min-colors 8) (background dark))
- (:foreground "yellow" :underline t))))
- "Face for fixed-with text like code snippets."
+(defface org-block-begin-line '((t (:inherit org-meta-line)))
+ "Face used for the line delimiting the begin of source blocks."
+ :group 'org-faces)
+
+(defface org-block-end-line '((t (:inherit org-block-begin-line)))
+ "Face used for the line delimiting the end of source blocks."
+ :group 'org-faces)
+
+(defface org-verbatim '((t (:inherit shadow)))
+ "Face for fixed-with text like code snippets"
:group 'org-faces
:version "22.1")
-(org-copy-face 'org-block 'org-quote
- "Face for #+BEGIN_QUOTE ... #+END_QUOTE blocks.")
-(org-copy-face 'org-block 'org-verse
- "Face for #+BEGIN_VERSE ... #+END_VERSE blocks.")
+(defface org-quote '((t (:inherit org-block)))
+ "Face for #+BEGIN_QUOTE ... #+END_QUOTE blocks."
+ :group 'org-faces)
+
+(defface org-verse '((t (:inherit org-block)))
+ "Face for #+BEGIN_VERSE ... #+END_VERSE blocks."
+ :group 'org-faces)
(defcustom org-fontify-quote-and-verse-blocks nil
"Non-nil means, add a special face to #+begin_quote and #+begin_verse block.
@@ -573,64 +429,64 @@ content of these blocks will still be treated as Org syntax."
:version "24.1"
:type 'boolean)
-(defface org-clock-overlay ;; copied from secondary-selection
- (org-compatible-face nil
- '((((class color) (min-colors 88) (background light))
- (:background "yellow1"))
- (((class color) (min-colors 88) (background dark))
- (:background "SkyBlue4"))
- (((class color) (min-colors 16) (background light))
- (:background "yellow"))
- (((class color) (min-colors 16) (background dark))
- (:background "SkyBlue4"))
- (((class color) (min-colors 8))
- (:background "cyan" :foreground "black"))
- (t (:inverse-video t))))
+(defface org-clock-overlay ;Copied from `secondary-selection'
+ '((((class color) (min-colors 88) (background light))
+ (:background "LightGray" :foreground "black"))
+ (((class color) (min-colors 88) (background dark))
+ (:background "SkyBlue4" :foreground "white"))
+ (((class color) (min-colors 16) (background light))
+ (:background "gray" :foreground "black"))
+ (((class color) (min-colors 16) (background dark))
+ (:background "SkyBlue4" :foreground "white"))
+ (((class color) (min-colors 8))
+ (:background "cyan" :foreground "black"))
+ (t (:inverse-video t)))
"Basic face for displaying the secondary selection."
:group 'org-faces)
-(defface org-agenda-structure ;; originally copied from font-lock-function-name-face
- (org-compatible-face nil
- '((((class color) (min-colors 88) (background light)) (:foreground "Blue1"))
- (((class color) (min-colors 88) (background dark)) (:foreground "LightSkyBlue"))
- (((class color) (min-colors 16) (background light)) (:foreground "Blue"))
- (((class color) (min-colors 16) (background dark)) (:foreground "LightSkyBlue"))
- (((class color) (min-colors 8)) (:foreground "blue" :bold t))
- (t (:bold t))))
+(defface org-agenda-structure ;Copied from `font-lock-function-name-face'
+ '((((class color) (min-colors 88) (background light)) (:foreground "Blue1"))
+ (((class color) (min-colors 88) (background dark)) (:foreground "LightSkyBlue"))
+ (((class color) (min-colors 16) (background light)) (:foreground "Blue"))
+ (((class color) (min-colors 16) (background dark)) (:foreground "LightSkyBlue"))
+ (((class color) (min-colors 8)) (:foreground "blue" :bold t))
+ (t (:bold t)))
"Face used in agenda for captions and dates."
:group 'org-faces)
-(org-copy-face 'org-agenda-structure 'org-agenda-date
- "Face used in agenda for normal days.")
+(defface org-agenda-date '((t (:inherit org-agenda-structure)))
+ "Face used in agenda for normal days."
+ :group 'org-faces)
-(org-copy-face 'org-agenda-date 'org-agenda-date-today
+(defface org-agenda-date-today
+ '((t (:inherit org-agenda-date :weight bold :italic t)))
"Face used in agenda for today."
- :weight 'bold :italic 't)
+ :group 'org-faces)
-(org-copy-face 'secondary-selection 'org-agenda-clocking
- "Face marking the current clock item in the agenda.")
+(defface org-agenda-clocking '((t (:inherit secondary-selection)))
+ "Face marking the current clock item in the agenda."
+ :group 'org-faces)
-(org-copy-face 'org-agenda-date 'org-agenda-date-weekend
+(defface org-agenda-date-weekend '((t (:inherit org-agenda-date :weight bold)))
"Face used in agenda for weekend days.
-See the variable `org-agenda-weekend-days' for a definition of which days
-belong to the weekend."
- :weight 'bold)
+
+See the variable `org-agenda-weekend-days' for a definition of
+which days belong to the weekend."
+ :group 'org-faces)
(defface org-scheduled
- (org-compatible-face nil
- '((((class color) (min-colors 88) (background light)) (:foreground "DarkGreen"))
- (((class color) (min-colors 88) (background dark)) (:foreground "PaleGreen"))
- (((class color) (min-colors 8)) (:foreground "green"))
- (t (:bold t :italic t))))
+ '((((class color) (min-colors 88) (background light)) (:foreground "DarkGreen"))
+ (((class color) (min-colors 88) (background dark)) (:foreground "PaleGreen"))
+ (((class color) (min-colors 8)) (:foreground "green"))
+ (t (:bold t :italic t)))
"Face for items scheduled for a certain day."
:group 'org-faces)
(defface org-scheduled-today
- (org-compatible-face nil
- '((((class color) (min-colors 88) (background light)) (:foreground "DarkGreen"))
- (((class color) (min-colors 88) (background dark)) (:foreground "PaleGreen"))
- (((class color) (min-colors 8)) (:foreground "green"))
- (t (:bold t :italic t))))
+ '((((class color) (min-colors 88) (background light)) (:foreground "DarkGreen"))
+ (((class color) (min-colors 88) (background dark)) (:foreground "PaleGreen"))
+ (((class color) (min-colors 8)) (:foreground "green"))
+ (t (:bold t :italic t)))
"Face for items scheduled for a certain day."
:group 'org-faces)
@@ -641,22 +497,20 @@ belong to the weekend."
:group 'org-faces)
(defface org-scheduled-previously
- (org-compatible-face nil
- '((((class color) (min-colors 88) (background light)) (:foreground "Firebrick"))
- (((class color) (min-colors 88) (background dark)) (:foreground "chocolate1"))
- (((class color) (min-colors 8) (background light)) (:foreground "red"))
- (((class color) (min-colors 8) (background dark)) (:foreground "red" :bold t))
- (t (:bold t))))
+ '((((class color) (min-colors 88) (background light)) (:foreground "Firebrick"))
+ (((class color) (min-colors 88) (background dark)) (:foreground "chocolate1"))
+ (((class color) (min-colors 8) (background light)) (:foreground "red"))
+ (((class color) (min-colors 8) (background dark)) (:foreground "red" :bold t))
+ (t (:bold t)))
"Face for items scheduled previously, and not yet done."
:group 'org-faces)
(defface org-upcoming-deadline
- (org-compatible-face nil
- '((((class color) (min-colors 88) (background light)) (:foreground "Firebrick"))
- (((class color) (min-colors 88) (background dark)) (:foreground "chocolate1"))
- (((class color) (min-colors 8) (background light)) (:foreground "red"))
- (((class color) (min-colors 8) (background dark)) (:foreground "red" :bold t))
- (t (:bold t))))
+ '((((class color) (min-colors 88) (background light)) (:foreground "Firebrick"))
+ (((class color) (min-colors 88) (background dark)) (:foreground "chocolate1"))
+ (((class color) (min-colors 8) (background light)) (:foreground "red"))
+ (((class color) (min-colors 8) (background dark)) (:foreground "red" :bold t))
+ (t (:bold t)))
"Face for items scheduled previously, and not yet done."
:group 'org-faces)
@@ -666,7 +520,7 @@ belong to the weekend."
(0.0 . default))
"Faces for showing deadlines in the agenda.
This is a list of cons cells. The cdr of each cell is a face to be used,
-and it can also just be like (:foreground \"yellow\").
+and it can also just be like \\='(:foreground \"yellow\").
Each car is a fraction of the head-warning time that must have passed for
this the face in the cdr to be used for display. The numbers must be
given in descending order. The head-warning time is normally taken
@@ -686,65 +540,61 @@ month and 365.24 days for a year)."
(sexp :tag "Face"))))
(defface org-agenda-restriction-lock
- (org-compatible-face nil
- '((((class color) (min-colors 88) (background light)) (:background "#eeeeee"))
- (((class color) (min-colors 88) (background dark)) (:background "#1C1C1C"))
- (((class color) (min-colors 16) (background light)) (:background "#eeeeee"))
- (((class color) (min-colors 16) (background dark)) (:background "#1C1C1C"))
- (((class color) (min-colors 8)) (:background "cyan" :foreground "black"))
- (t (:inverse-video t))))
+ '((((class color) (min-colors 88) (background light)) (:background "#eeeeee"))
+ (((class color) (min-colors 88) (background dark)) (:background "#1C1C1C"))
+ (((class color) (min-colors 16) (background light)) (:background "#eeeeee"))
+ (((class color) (min-colors 16) (background dark)) (:background "#1C1C1C"))
+ (((class color) (min-colors 8)) (:background "cyan" :foreground "black"))
+ (t (:inverse-video t)))
"Face for showing the agenda restriction lock."
:group 'org-faces)
-(defface org-agenda-filter-tags
- (org-compatible-face 'mode-line nil)
+(defface org-agenda-filter-tags '((t :inherit mode-line))
"Face for tag(s) in the mode-line when filtering the agenda."
:group 'org-faces)
-(defface org-agenda-filter-regexp
- (org-compatible-face 'mode-line nil)
+(defface org-agenda-filter-regexp '((t :inherit mode-line))
"Face for regexp(s) in the mode-line when filtering the agenda."
:group 'org-faces)
-(defface org-agenda-filter-category
- (org-compatible-face 'mode-line nil)
- "Face for categories(s) in the mode-line when filtering the agenda."
+(defface org-agenda-filter-category '((t :inherit mode-line))
+ "Face for categories in the mode-line when filtering the agenda."
:group 'org-faces)
-(defface org-time-grid ;; originally copied from font-lock-variable-name-face
- (org-compatible-face nil
- '((((class color) (min-colors 16) (background light)) (:foreground "DarkGoldenrod"))
- (((class color) (min-colors 16) (background dark)) (:foreground "LightGoldenrod"))
- (((class color) (min-colors 8)) (:foreground "yellow" :weight light))))
+(defface org-agenda-filter-effort '((t :inherit mode-line))
+ "Face for effort in the mode-line when filtering the agenda."
+ :group 'org-faces)
+
+(defface org-time-grid ;Copied from `font-lock-variable-name-face'
+ '((((class color) (min-colors 16) (background light)) (:foreground "DarkGoldenrod"))
+ (((class color) (min-colors 16) (background dark)) (:foreground "LightGoldenrod"))
+ (((class color) (min-colors 8)) (:foreground "yellow" :weight light)))
"Face used for time grids."
:group 'org-faces)
-(org-copy-face 'org-time-grid 'org-agenda-current-time
- "Face used to show the current time in the time grid.")
+(defface org-agenda-current-time '((t (:inherit org-time-grid)))
+ "Face used to show the current time in the time grid."
+ :group 'org-faces)
-(defface org-agenda-diary
- (org-compatible-face 'default nil)
+(defface org-agenda-diary '((t :inherit default))
"Face used for agenda entries that come from the Emacs diary."
:group 'org-faces)
-(defface org-agenda-calendar-event
- (org-compatible-face 'default nil)
+(defface org-agenda-calendar-event '((t :inherit default))
"Face used to show events and appointments in the agenda."
:group 'org-faces)
-(defface org-agenda-calendar-sexp
- (org-compatible-face 'default nil)
+(defface org-agenda-calendar-sexp '((t :inherit default))
"Face used to show events computed from a S-expression."
:group 'org-faces)
(defconst org-level-faces
'(org-level-1 org-level-2 org-level-3 org-level-4
- org-level-5 org-level-6 org-level-7 org-level-8
- ))
+ org-level-5 org-level-6 org-level-7 org-level-8))
(defcustom org-n-level-faces (length org-level-faces)
"The number of different faces to be used for headlines.
-Org-mode defines 8 different headline faces, so this can be at most 8.
+Org mode defines 8 different headline faces, so this can be at most 8.
If it is less than 8, the level-1 face gets re-used for level N+1 etc."
:type 'integer
:group 'org-faces)
@@ -777,25 +627,26 @@ level org-n-level-faces"
:version "24.4"
:package-version '(Org . "8.0"))
-(defface org-macro
- (org-compatible-face 'org-latex-and-related nil)
+(defface org-macro '((t :inherit org-latex-and-related))
"Face for macros."
:group 'org-faces
:version "24.4"
:package-version '(Org . "8.0"))
-(defface org-tag-group
- (org-compatible-face 'org-tag nil)
+(defface org-tag-group '((t :inherit org-tag))
"Face for group tags."
:group 'org-faces
:version "24.4"
:package-version '(Org . "8.0"))
-(org-copy-face 'mode-line 'org-mode-line-clock
- "Face used for clock display in mode line.")
-(org-copy-face 'mode-line 'org-mode-line-clock-overrun
+(defface org-mode-line-clock '((t (:inherit mode-line)))
+ "Face used for clock display in mode line."
+ :group 'org-faces)
+
+(defface org-mode-line-clock-overrun
+ '((t (:inherit mode-line :background "red")))
"Face used for clock display for overrun tasks in mode line."
- :background "red")
+ :group 'org-faces)
(provide 'org-faces)
diff --git a/lisp/org/org-feed.el b/lisp/org/org-feed.el
index cfb4b4f7e33..6ebe5ecf5dc 100644
--- a/lisp/org/org-feed.el
+++ b/lisp/org/org-feed.el
@@ -1,4 +1,4 @@
-;;; org-feed.el --- Add RSS feed items to Org files
+;;; org-feed.el --- Add RSS feed items to Org files -*- lexical-binding: t; -*-
;;
;; Copyright (C) 2009-2017 Free Software Foundation, Inc.
;;
@@ -24,11 +24,11 @@
;;
;;; Commentary:
;;
-;; This module allows entries to be created and changed in an Org-mode
-;; file triggered by items in an RSS feed. The basic functionality is
-;; geared toward simply adding new items found in a feed as outline nodes
-;; to an Org file. Using hooks, arbitrary actions can be triggered for
-;; new or changed items.
+;; This module allows entries to be created and changed in an Org mode
+;; file triggered by items in an RSS feed. The basic functionality
+;; is geared toward simply adding new items found in a feed as
+;; outline nodes to an Org file. Using hooks, arbitrary actions can
+;; be triggered for new or changed items.
;;
;; Selecting feeds and target locations
;; ------------------------------------
@@ -77,10 +77,8 @@
;; org-feed.el needs to keep track of which feed items have been handled
;; before, so that they will not be handled again. For this, org-feed.el
;; stores information in a special drawer, FEEDSTATUS, under the heading
-;; that received the input of the feed. You should add FEEDSTATUS
-;; to your list of drawers in the files that receive feed input:
+;; that received the input of the feed.
;;
-;; #+DRAWERS: PROPERTIES CLOCK LOGBOOK RESULTS FEEDSTATUS
;;
;; Acknowledgments
;; ---------------
@@ -102,8 +100,8 @@
(declare-function xml-substitute-special "xml" (string))
(declare-function org-capture-escaped-% "org-capture" ())
+(declare-function org-capture-expand-embedded-elisp "org-capture" (&optional mark))
(declare-function org-capture-inside-embedded-elisp-p "org-capture" ())
-(declare-function org-capture-expand-embedded-elisp "org-capture" ())
(defgroup org-feed nil
"Options concerning RSS feeds as inputs for Org files."
@@ -117,7 +115,9 @@ to create inbox items in Org. Each entry is a list with the following items:
name a custom name for this feed
URL the Feed URL
-file the target Org file where entries should be listed
+file the target Org file where entries should be listed, when
+ nil the target becomes the current buffer (may be an
+ indirect buffer) each time the feed update is invoked
headline the headline under which entries should be listed
Additional arguments can be given using keyword-value pairs. Many of these
@@ -216,10 +216,7 @@ Here are the keyword-value pair allows in `org-feed-alist'.
(defcustom org-feed-drawer "FEEDSTATUS"
"The name of the drawer for feed status information.
Each feed may also specify its own drawer name using the `:drawer'
-parameter in `org-feed-alist'.
-Note that in order to make these drawers behave like drawers, they must
-be added to the variable `org-drawers' or configured with a #+DRAWERS
-line."
+parameter in `org-feed-alist'."
:group 'org-feed
:type '(string :tag "Drawer Name"))
@@ -300,7 +297,8 @@ it can be a list structured like an entry in `org-feed-alist'."
(catch 'exit
(let ((name (car feed))
(url (nth 1 feed))
- (file (nth 2 feed))
+ (file (or (nth 2 feed) (buffer-file-name (or (buffer-base-buffer)
+ (current-buffer)))))
(headline (nth 3 feed))
(filter (nth 1 (memq :filter feed)))
(formatter (nth 1 (memq :formatter feed)))
@@ -315,7 +313,7 @@ it can be a list structured like an entry in `org-feed-alist'."
(parse-entry (or (nth 1 (memq :parse-entry feed))
'org-feed-parse-rss-entry))
feed-buffer inbox-pos new-formatted
- entries old-status status new changed guid-alist e guid olds)
+ entries old-status status new changed guid-alist guid olds)
(setq feed-buffer (org-feed-get-feed url))
(unless (and feed-buffer (bufferp (get-buffer feed-buffer)))
(error "Cannot get feed %s" name))
@@ -407,8 +405,8 @@ it can be a list structured like an entry in `org-feed-alist'."
;; Normalize the visibility of the inbox tree
(goto-char inbox-pos)
- (hide-subtree)
- (show-children)
+ (outline-hide-subtree)
+ (org-show-children)
(org-cycle-hide-drawers 'children)
;; Hooks and messages
@@ -442,7 +440,7 @@ it can be a list structured like an entry in `org-feed-alist'."
(if (stringp feed) (setq feed (assoc feed org-feed-alist)))
(unless feed
(error "No such feed in `org-feed-alist"))
- (org-pop-to-buffer-same-window
+ (pop-to-buffer-same-window
(org-feed-update feed 'retrieve-only))
(goto-char (point-min)))
@@ -477,8 +475,7 @@ This will find DRAWER and extract the alist."
"Write the feed STATUS to DRAWER in entry at POS."
(save-excursion
(goto-char pos)
- (let ((end (save-excursion (org-end-of-subtree t t)))
- guid)
+ (let ((end (save-excursion (org-end-of-subtree t t))))
(if (re-search-forward (concat "^[ \t]*:" drawer ":[ \t]*\n")
end t)
(progn
@@ -514,66 +511,77 @@ ENTRY is a property list. This function adds a `:formatted-for-org' property
and returns the full property list.
If that property is already present, nothing changes."
(require 'org-capture)
- (if formatter
- (funcall formatter entry)
- (let (dlines time escape name tmp
- v-h v-t v-T v-u v-U v-a)
- (setq dlines (org-split-string (or (plist-get entry :description) "???")
- "\n")
- v-h (or (plist-get entry :title) (car dlines) "???")
- time (or (if (plist-get entry :pubDate)
- (org-read-date t t (plist-get entry :pubDate)))
- (current-time))
- v-t (format-time-string (org-time-stamp-format nil nil) time)
- v-T (format-time-string (org-time-stamp-format t nil) time)
- v-u (format-time-string (org-time-stamp-format nil t) time)
- v-U (format-time-string (org-time-stamp-format t t) time)
- v-a (if (setq tmp (or (and (plist-get entry :guid-permalink)
- (plist-get entry :guid))
- (plist-get entry :link)))
- (concat "[[" tmp "]]\n")
- ""))
+ (if formatter (funcall formatter entry)
+ (let* ((dlines
+ (org-split-string (or (plist-get entry :description) "???")
+ "\n"))
+ (time (or (if (plist-get entry :pubDate)
+ (org-read-date t t (plist-get entry :pubDate)))
+ (current-time)))
+ (v-h (or (plist-get entry :title) (car dlines) "???"))
+ (v-t (format-time-string (org-time-stamp-format nil nil) time))
+ (v-T (format-time-string (org-time-stamp-format t nil) time))
+ (v-u (format-time-string (org-time-stamp-format nil t) time))
+ (v-U (format-time-string (org-time-stamp-format t t) time))
+ (v-a (let ((tmp (or (and (plist-get entry :guid-permalink)
+ (plist-get entry :guid))
+ (plist-get entry :link))))
+ (if tmp (format "[[%s]]\n" tmp ) ""))))
(with-temp-buffer
- (insert template)
-
- ;; Simple %-escapes
- ;; before embedded elisp to support simple %-escapes as
- ;; arguments for embedded elisp
- (goto-char (point-min))
- (while (re-search-forward "%\\([a-zA-Z]+\\)" nil t)
- (unless (org-capture-escaped-%)
- (setq name (match-string 1)
- escape (org-capture-inside-embedded-elisp-p))
- (cond
- ((member name '("h" "t" "T" "u" "U" "a"))
- (setq tmp (symbol-value (intern (concat "v-" name)))))
- ((setq tmp (plist-get entry (intern (concat ":" name))))
- (save-excursion
- (save-match-data
- (beginning-of-line 1)
- (when (looking-at
- (concat "^\\([ \t]*\\)%" name "[ \t]*$"))
- (setq tmp (org-feed-make-indented-block
- tmp (org-get-indentation))))))))
- (when tmp
- ;; escape string delimiters `"' when inside %() embedded lisp
- (when escape
- (setq tmp (replace-regexp-in-string "\"" "\\\\\"" tmp)))
- (replace-match tmp t t))))
-
- ;; %() embedded elisp
- (org-capture-expand-embedded-elisp)
-
- (decode-coding-string
- (buffer-string) (detect-coding-region (point-min) (point-max) t))))))
+ (insert template)
+ (goto-char (point-min))
+
+ ;; Mark %() embedded elisp for later evaluation.
+ (org-capture-expand-embedded-elisp 'mark)
+
+ ;; Simple %-escapes. `org-capture-escaped-%' may modify
+ ;; buffer and cripple match-data. Use markers instead.
+ (while (re-search-forward "%\\([a-zA-Z]+\\)" nil t)
+ (let ((key (match-string 1))
+ (beg (copy-marker (match-beginning 0)))
+ (end (copy-marker (match-end 0))))
+ (unless (org-capture-escaped-%)
+ (delete-region beg end)
+ (set-marker beg nil)
+ (set-marker end nil)
+ (let ((replacement
+ (pcase key
+ ("h" v-h)
+ ("t" v-t)
+ ("T" v-T)
+ ("u" v-u)
+ ("U" v-U)
+ ("a" v-a)
+ (name
+ (let ((v (plist-get entry (intern (concat ":" name)))))
+ (save-excursion
+ (save-match-data
+ (beginning-of-line)
+ (if (looking-at
+ (concat "^\\([ \t]*\\)%" name "[ \t]*$"))
+ (org-feed-make-indented-block
+ v (org-get-indentation))
+ v))))))))
+ (when replacement
+ (insert
+ ;; Escape string delimiters within embedded lisp.
+ (if (org-capture-inside-embedded-elisp-p)
+ (replace-regexp-in-string "\"" "\\\\\"" replacement)
+ replacement)))))))
+
+ ;; %() embedded elisp
+ (org-capture-expand-embedded-elisp)
+
+ (decode-coding-string
+ (buffer-string) (detect-coding-region (point-min) (point-max) t))))))
(defun org-feed-make-indented-block (s n)
"Add indentation of N spaces to a multiline string S."
(if (not (string-match "\n" s))
s
(mapconcat 'identity
- (org-split-string s "\n")
- (concat "\n" (make-string n ?\ )))))
+ (org-split-string s "\n")
+ (concat "\n" (make-string n ?\ )))))
(defun org-feed-skip-http-headers (buffer)
"Remove HTTP headers from BUFFER, and return it.
@@ -605,6 +613,7 @@ Assumes headers are indeed present!"
"Parse BUFFER for RSS feed entries.
Returns a list of entries, with each entry a property list,
containing the properties `:guid' and `:item-full-text'."
+ (require 'xml)
(let ((case-fold-search t)
entries beg end item guid entry)
(with-current-buffer buffer
@@ -616,7 +625,7 @@ containing the properties `:guid' and `:item-full-text'."
(match-beginning 0)))
(setq item (buffer-substring beg end)
guid (if (string-match ".*?>\\(.*?\\)" item)
- (org-match-string-no-properties 1 item)))
+ (xml-substitute-special (match-string-no-properties 1 item))))
(setq entry (list :guid guid :item-full-text item))
(push entry entries)
(widen)
diff --git a/lisp/org/org-footnote.el b/lisp/org/org-footnote.el
index 553f1240425..b9f23f144f7 100644
--- a/lisp/org/org-footnote.el
+++ b/lisp/org/org-footnote.el
@@ -1,4 +1,4 @@
-;;; org-footnote.el --- Footnote support in Org and elsewhere
+;;; org-footnote.el --- Footnote support in Org -*- lexical-binding: t; -*-
;;
;; Copyright (C) 2009-2017 Free Software Foundation, Inc.
;;
@@ -24,72 +24,68 @@
;;
;;; Commentary:
-;; This file contains the code dealing with footnotes in Org-mode.
-;; The code can also be used in arbitrary text modes to provide
-;; footnotes. Compared to Steven L Baur's footnote.el it provides
-;; better support for resuming editing. It is less configurable than
-;; Steve's code, though.
+;; This file contains the code dealing with footnotes in Org mode.
;;; Code:
-(eval-when-compile
- (require 'cl))
+;;;; Declarations
+
+(require 'cl-lib)
(require 'org-macs)
(require 'org-compat)
-(declare-function message-point-in-header-p "message" ())
+(declare-function org-at-comment-p "org" ())
+(declare-function org-at-heading-p "org" (&optional ignored))
(declare-function org-back-over-empty-lines "org" ())
-(declare-function org-back-to-heading "org" (&optional invisible-ok))
-(declare-function org-combine-plists "org" (&rest plists))
+(declare-function org-edit-footnote-reference "org-src" ())
+(declare-function org-element-at-point "org-element" ())
+(declare-function org-element-class "org-element" (datum &optional parent))
+(declare-function org-element-context "org-element" (&optional element))
+(declare-function org-element-lineage "org-element" (blob &optional types with-self))
+(declare-function org-element-property "org-element" (property element))
+(declare-function org-element-type "org-element" (element))
(declare-function org-end-of-subtree "org" (&optional invisible-ok to-heading))
(declare-function org-fill-paragraph "org" (&optional justify))
-(declare-function org-icompleting-read "org" (&rest args))
-(declare-function org-id-uuid "org-id" ())
(declare-function org-in-block-p "org" (names))
-(declare-function org-in-commented-line "org" ())
-(declare-function org-in-indented-comment-line "org" ())
(declare-function org-in-regexp "org" (re &optional nlines visually))
(declare-function org-in-verbatim-emphasis "org" ())
(declare-function org-inside-LaTeX-fragment-p "org" ())
(declare-function org-inside-latex-macro-p "org" ())
(declare-function org-mark-ring-push "org" (&optional pos buffer))
(declare-function org-show-context "org" (&optional key))
-(declare-function org-trim "org" (s))
-(declare-function org-skip-whitespace "org" ())
+(declare-function org-trim "org" (s &optional keep-lead))
(declare-function outline-next-heading "outline")
-(declare-function org-skip-whitespace "org" ())
-(defvar org-outline-regexp-bol) ; defined in org.el
-(defvar org-odd-levels-only) ; defined in org.el
+(defvar electric-indent-mode)
+(defvar org-blank-before-new-entry) ; defined in org.el
(defvar org-bracket-link-regexp) ; defined in org.el
-(defvar message-cite-prefix-regexp) ; defined in message.el
-(defvar message-signature-separator) ; defined in message.el
+(defvar org-complex-heading-regexp) ; defined in org.el
+(defvar org-odd-levels-only) ; defined in org.el
+(defvar org-outline-regexp) ; defined in org.el
+(defvar org-outline-regexp-bol) ; defined in org.el
+
+
+;;;; Constants
(defconst org-footnote-re
- ;; Only [1]-like footnotes are closed in this regexp, as footnotes
- ;; from other types might contain square brackets (i.e. links) in
- ;; their definition.
- ;;
- ;; `org-re' is used for regexp compatibility with XEmacs.
- (concat "\\[\\(?:"
- ;; Match inline footnotes.
- (org-re "fn:\\([-_[:word:]]+\\)?:\\|")
- ;; Match other footnotes.
- "\\(?:\\([0-9]+\\)\\]\\)\\|"
- (org-re "\\(fn:[-_[:word:]]+\\)")
- "\\)")
- "Regular expression for matching footnotes.")
-
-(defconst org-footnote-definition-re
- (org-re "^\\[\\([0-9]+\\|fn:[-_[:word:]]+\\)\\]")
- "Regular expression matching the definition of a footnote.")
-
-(defconst org-footnote-forbidden-blocks
- '("ascii" "beamer" "comment" "example" "html" "latex" "odt" "src")
+ "\\[fn:\\(?:\\(?1:[-_[:word:]]+\\)?\\(:\\)\\|\\(?1:[-_[:word:]]+\\)\\]\\)"
+ "Regular expression for matching footnotes.
+Match group 1 contains footnote's label. It is nil for anonymous
+footnotes. Match group 2 is non-nil only when footnote is
+inline, i.e., it contains its own definition.")
+
+(defconst org-footnote-definition-re "^\\[fn:\\([-_[:word:]]+\\)\\]"
+ "Regular expression matching the definition of a footnote.
+Match group 1 contains definition's label.")
+
+(defconst org-footnote-forbidden-blocks '("comment" "example" "export" "src")
"Names of blocks where footnotes are not allowed.")
+
+;;;; Customization
+
(defgroup org-footnote nil
- "Footnotes in Org-mode."
+ "Footnotes in Org mode."
:tag "Org Footnote"
:group 'org)
@@ -106,25 +102,21 @@ the notes. However, by hand you may place definitions
*anywhere*.
If this is a string, during export, all subtrees starting with
-this heading will be ignored."
- :group 'org-footnote
- :type '(choice
- (string :tag "Collect footnotes under heading")
- (const :tag "Define footnotes locally" nil)))
+this heading will be ignored.
-(defcustom org-footnote-tag-for-non-org-mode-files "Footnotes:"
- "Tag marking the beginning of footnote section.
-The Org footnote engine can be used in arbitrary text files as well
-as in Org-mode. Outside Org mode, new footnotes are always placed at
-the end of the file. When you normalize the notes, any line containing
-only this tag will be removed, a new one will be inserted at the end
-of the file, followed by the collected and normalized footnotes.
+If you don't use the customize interface to change this variable,
+you will need to run the following command after the change:
-If you don't want any tag in such buffers, set this variable to nil."
+ `\\[universal-argument] \\[org-element-cache-reset]'"
:group 'org-footnote
+ :initialize 'custom-initialize-default
+ :set (lambda (var val)
+ (set var val)
+ (when (fboundp 'org-element-cache-reset)
+ (org-element-cache-reset 'all)))
:type '(choice
- (string :tag "Collect footnotes under tag")
- (const :tag "Don't use a tag" nil)))
+ (string :tag "Collect footnotes under heading")
+ (const :tag "Define footnotes locally" nil)))
(defcustom org-footnote-define-inline nil
"Non-nil means define footnotes inline, at reference location.
@@ -143,15 +135,13 @@ t Create unique labels of the form [fn:1], [fn:2], etc.
confirm Like t, but let the user edit the created value.
The label can be removed from the minibuffer to create
an anonymous footnote.
-random Automatically generate a unique, random label.
-plain Automatically create plain number labels like [1]."
+random Automatically generate a unique, random label."
:group 'org-footnote
:type '(choice
(const :tag "Prompt for label" nil)
(const :tag "Create automatic [fn:N]" t)
(const :tag "Offer automatic [fn:N] for editing" confirm)
- (const :tag "Create a random label" random)
- (const :tag "Create automatic [N]" plain)))
+ (const :tag "Create a random label" random)))
(defcustom org-footnote-auto-adjust nil
"Non-nil means automatically adjust footnotes after insert/delete.
@@ -179,23 +169,19 @@ extracted will be filled again."
:group 'org-footnote
:type 'boolean)
+
+;;;; Predicates
+
(defun org-footnote-in-valid-context-p ()
"Is point in a context where footnotes are allowed?"
(save-match-data
- (not (or (org-in-commented-line)
- (org-in-indented-comment-line)
+ (not (or (org-at-comment-p)
(org-inside-LaTeX-fragment-p)
;; Avoid literal example.
(org-in-verbatim-emphasis)
(save-excursion
(beginning-of-line)
(looking-at "[ \t]*:[ \t]+"))
- ;; Avoid cited text and headers in message-mode.
- (and (derived-mode-p 'message-mode)
- (or (save-excursion
- (beginning-of-line)
- (looking-at message-cite-prefix-regexp))
- (message-point-in-header-p)))
;; Avoid forbidden blocks.
(org-in-block-p org-footnote-forbidden-blocks)))))
@@ -208,13 +194,9 @@ positions, and the definition, when inlined."
(or (looking-at org-footnote-re)
(org-in-regexp org-footnote-re)
(save-excursion (re-search-backward org-footnote-re nil t)))
- (/= (match-beginning 0) (point-at-bol)))
+ (/= (match-beginning 0) (line-beginning-position)))
(let* ((beg (match-beginning 0))
- (label (or (org-match-string-no-properties 2)
- (org-match-string-no-properties 3)
- ;; Anonymous footnotes don't have labels
- (and (match-string 1)
- (concat "fn:" (org-match-string-no-properties 1)))))
+ (label (match-string-no-properties 1))
;; Inline footnotes don't end at (match-end 0) as
;; `org-footnote-re' stops just after the second colon.
;; Find the real ending with `scan-sexps', so Org doesn't
@@ -222,7 +204,8 @@ positions, and the definition, when inlined."
(end (ignore-errors (scan-sexps beg 1))))
;; Point is really at a reference if it's located before true
;; ending of the footnote.
- (when (and end (< (point) end)
+ (when (and end
+ (< (point) end)
;; Verify match isn't a part of a link.
(not (save-excursion
(goto-char beg)
@@ -234,16 +217,17 @@ positions, and the definition, when inlined."
(not (org-inside-latex-macro-p)))
(list label beg end
;; Definition: ensure this is an inline footnote first.
- (and (or (not label) (match-string 1))
- (org-trim (buffer-substring-no-properties
- (match-end 0) (1- end)))))))))
+ (and (match-end 2)
+ (org-trim
+ (buffer-substring-no-properties
+ (match-end 0) (1- end)))))))))
(defun org-footnote-at-definition-p ()
"Is point within a footnote definition?
This matches only pure definitions like [1] or [fn:name] at the
beginning of a line. It does not match references like
-[fn:name:definition], where the footnote text is included and
+\[fn:name:definition], where the footnote text is included and
defined locally.
The return value will be nil if not at a footnote definition, and
@@ -259,26 +243,224 @@ otherwise."
(concat org-outline-regexp-bol
"\\|^\\([ \t]*\n\\)\\{2,\\}") nil t))))
(when (re-search-backward org-footnote-definition-re lim t)
- (let ((label (org-match-string-no-properties 1))
+ (let ((label (match-string-no-properties 1))
(beg (match-beginning 0))
(beg-def (match-end 0))
- ;; In message-mode, do not search after signature.
- (end (let ((bound (and (derived-mode-p 'message-mode)
- (save-excursion
- (goto-char (point-max))
- (re-search-backward
- message-signature-separator nil t)))))
- (if (progn
- (end-of-line)
- (re-search-forward
- (concat org-outline-regexp-bol "\\|"
- org-footnote-definition-re "\\|"
- "^\\([ \t]*\n\\)\\{2,\\}") bound 'move))
- (match-beginning 0)
- (point)))))
+ (end (if (progn
+ (end-of-line)
+ (re-search-forward
+ (concat org-outline-regexp-bol "\\|"
+ org-footnote-definition-re "\\|"
+ "^\\([ \t]*\n\\)\\{2,\\}") nil 'move))
+ (match-beginning 0)
+ (point))))
(list label beg end
(org-trim (buffer-substring-no-properties beg-def end)))))))))
+
+;;;; Internal functions
+
+(defun org-footnote--allow-reference-p ()
+ "Non-nil when a footnote reference can be inserted at point."
+ ;; XXX: This is similar to `org-footnote-in-valid-context-p' but
+ ;; more accurate and usually faster, except in some corner cases.
+ ;; It may replace it after doing proper benchmarks as it would be
+ ;; used in fontification.
+ (unless (bolp)
+ (let* ((context (org-element-context))
+ (type (org-element-type context)))
+ (cond
+ ;; No footnote reference in attributes.
+ ((let ((post (org-element-property :post-affiliated context)))
+ (and post (< (point) post)))
+ nil)
+ ;; Paragraphs and blank lines at top of document are fine.
+ ((memq type '(nil paragraph)))
+ ;; So are contents of verse blocks.
+ ((eq type 'verse-block)
+ (and (>= (point) (org-element-property :contents-begin context))
+ (< (point) (org-element-property :contents-end context))))
+ ;; In an headline or inlinetask, point must be either on the
+ ;; heading itself or on the blank lines below.
+ ((memq type '(headline inlinetask))
+ (or (not (org-at-heading-p))
+ (and (save-excursion
+ (beginning-of-line)
+ (and (let ((case-fold-search t))
+ (not (looking-at-p "\\*+ END[ \t]*$")))
+ (let ((case-fold-search nil))
+ (looking-at org-complex-heading-regexp))))
+ (match-beginning 4)
+ (>= (point) (match-beginning 4))
+ (or (not (match-beginning 5))
+ (< (point) (match-beginning 5))))))
+ ;; White spaces after an object or blank lines after an element
+ ;; are OK.
+ ((>= (point)
+ (save-excursion (goto-char (org-element-property :end context))
+ (skip-chars-backward " \r\t\n")
+ (if (eq (org-element-class context) 'object) (point)
+ (1+ (line-beginning-position 2))))))
+ ;; Other elements are invalid.
+ ((eq (org-element-class context) 'element) nil)
+ ;; Just before object is fine.
+ ((= (point) (org-element-property :begin context)))
+ ;; Within recursive object too, but not in a link.
+ ((eq type 'link) nil)
+ ((let ((cbeg (org-element-property :contents-begin context))
+ (cend (org-element-property :contents-end context)))
+ (and cbeg (>= (point) cbeg) (<= (point) cend))))))))
+
+(defun org-footnote--clear-footnote-section ()
+ "Remove all footnote sections in buffer and create a new one.
+New section is created at the end of the buffer, before any file
+local variable definition. Leave point within the new section."
+ (when org-footnote-section
+ (goto-char (point-min))
+ (let ((regexp
+ (format "^\\*+ +%s[ \t]*$"
+ (regexp-quote org-footnote-section))))
+ (while (re-search-forward regexp nil t)
+ (delete-region
+ (match-beginning 0)
+ (progn (org-end-of-subtree t t)
+ (if (not (eobp)) (point)
+ (org-footnote--goto-local-insertion-point)
+ (skip-chars-forward " \t\n")
+ (if (eobp) (point) (line-beginning-position)))))))
+ (goto-char (point-max))
+ (org-footnote--goto-local-insertion-point)
+ (when (and (cdr (assq 'heading org-blank-before-new-entry))
+ (zerop (save-excursion (org-back-over-empty-lines))))
+ (insert "\n"))
+ (insert "* " org-footnote-section "\n")))
+
+(defun org-footnote--set-label (label)
+ "Set label of footnote at point to string LABEL.
+Assume point is at the beginning of the reference or definition
+to rename."
+ (forward-char 4)
+ (cond ((eq (char-after) ?:) (insert label))
+ ((looking-at "\\([-_[:word:]]+\\)") (replace-match label nil nil nil 1))
+ (t nil)))
+
+(defun org-footnote--collect-references (&optional anonymous)
+ "Collect all labeled footnote references in current buffer.
+
+Return an alist where associations follow the pattern
+
+ (LABEL MARKER TOP-LEVEL SIZE)
+
+with
+
+ LABEL the label of the of the definition,
+ MARKER a marker pointing to its beginning,
+ TOP-LEVEL a boolean, nil when the footnote is contained within
+ another one,
+ SIZE the length of the inline definition, in characters,
+ or nil for non-inline references.
+
+When optional ANONYMOUS is non-nil, also collect anonymous
+references. In such cases, LABEL is nil.
+
+References are sorted according to a deep-reading order."
+ (org-with-wide-buffer
+ (goto-char (point-min))
+ (let ((regexp (if anonymous org-footnote-re "\\[fn:[-_[:word:]]+[]:]"))
+ references nested)
+ (save-excursion
+ (while (re-search-forward regexp nil t)
+ ;; Ignore definitions.
+ (unless (and (eq (char-before) ?\])
+ (= (line-beginning-position) (match-beginning 0)))
+ ;; Ensure point is within the reference before parsing it.
+ (backward-char)
+ (let ((object (org-element-context)))
+ (when (eq (org-element-type object) 'footnote-reference)
+ (let* ((label (org-element-property :label object))
+ (begin (org-element-property :begin object))
+ (size
+ (and (eq (org-element-property :type object) 'inline)
+ (- (org-element-property :contents-end object)
+ (org-element-property :contents-begin object)))))
+ (let ((d (org-element-lineage object '(footnote-definition))))
+ (push (list label (copy-marker begin) (not d) size)
+ references)
+ (when d
+ ;; Nested references are stored in alist NESTED.
+ ;; Associations there follow the pattern
+ ;;
+ ;; (DEFINITION-LABEL . REFERENCES)
+ (let* ((def-label (org-element-property :label d))
+ (labels (assoc def-label nested)))
+ (if labels (push label (cdr labels))
+ (push (list def-label label) nested)))))))))))
+ ;; Sort the list of references. Nested footnotes have priority
+ ;; over top-level ones.
+ (letrec ((ordered nil)
+ (add-reference
+ (lambda (ref allow-nested)
+ (when (or allow-nested (nth 2 ref))
+ (push ref ordered)
+ (dolist (r (mapcar (lambda (l) (assoc l references))
+ (reverse
+ (cdr (assoc (nth 0 ref) nested)))))
+ (funcall add-reference r t))))))
+ (dolist (r (reverse references) (nreverse ordered))
+ (funcall add-reference r nil))))))
+
+(defun org-footnote--collect-definitions (&optional delete)
+ "Collect all footnote definitions in current buffer.
+
+Return an alist where associations follow the pattern
+
+ (LABEL . DEFINITION)
+
+with LABEL and DEFINITION being, respectively, the label and the
+definition of the footnote, as strings.
+
+When optional argument DELETE is non-nil, delete the definition
+while collecting them."
+ (org-with-wide-buffer
+ (goto-char (point-min))
+ (let (definitions seen)
+ (while (re-search-forward org-footnote-definition-re nil t)
+ (backward-char)
+ (let ((element (org-element-at-point)))
+ (let ((label (org-element-property :label element)))
+ (when (and (eq (org-element-type element) 'footnote-definition)
+ (not (member label seen)))
+ (push label seen)
+ (let* ((beg (progn
+ (goto-char (org-element-property :begin element))
+ (skip-chars-backward " \r\t\n")
+ (if (bobp) (point) (line-beginning-position 2))))
+ (end (progn
+ (goto-char (org-element-property :end element))
+ (skip-chars-backward " \r\t\n")
+ (line-beginning-position 2)))
+ (def (org-trim (buffer-substring-no-properties beg end))))
+ (push (cons label def) definitions)
+ (when delete (delete-region beg end)))))))
+ definitions)))
+
+(defun org-footnote--goto-local-insertion-point ()
+ "Find insertion point for footnote, just before next outline heading.
+Assume insertion point is within currently accessible part of the buffer."
+ (org-with-limited-levels (outline-next-heading))
+ ;; Skip file local variables. See `modify-file-local-variable'.
+ (when (eobp)
+ (let ((case-fold-search t))
+ (re-search-backward "^[ \t]*# +Local Variables:"
+ (max (- (point-max) 3000) (point-min))
+ t)))
+ (skip-chars-backward " \t\n")
+ (forward-line)
+ (unless (bolp) (insert "\n")))
+
+
+;;;; Navigation
+
(defun org-footnote-get-next-reference (&optional label backward limit)
"Return complete reference of the next footnote.
@@ -289,7 +471,7 @@ the buffer position bounding the search.
Return value is a list like those provided by `org-footnote-at-reference-p'.
If no footnote is found, return nil."
(save-excursion
- (let* ((label-fmt (if label (format "\\[%s[]:]" label) org-footnote-re)))
+ (let* ((label-fmt (if label (format "\\[fn:%s[]:]" label) org-footnote-re)))
(catch 'exit
(while t
(unless (funcall (if backward #'re-search-backward #'re-search-forward)
@@ -313,59 +495,54 @@ If no footnote is found, return nil."
(unless (re-search-forward org-footnote-re limit t)
(goto-char origin)
(throw 'exit nil))
- ;; Beware: with [1]-like footnotes point will be just after
+ ;; Beware: with non-inline footnotes point will be just after
;; the closing square bracket.
(backward-char)
(cond
((setq ref (org-footnote-at-reference-p))
(throw 'exit ref))
- ;; Definition: also grab the last square bracket, only
- ;; matched in `org-footnote-re' for [1]-like footnotes.
+ ;; Definition: also grab the last square bracket, matched in
+ ;; `org-footnote-re' for non-inline footnotes.
((save-match-data (org-footnote-at-definition-p))
(let ((end (match-end 0)))
(throw 'exit
(list nil (match-beginning 0)
- (if (eq (char-before end) 93) end (1+ end)))))))))))
+ (if (eq (char-before end) ?\]) end (1+ end)))))))))))
-(defun org-footnote-get-definition (label)
- "Return label, boundaries and definition of the footnote LABEL."
- (let* ((label (regexp-quote (org-footnote-normalize-label label)))
- (re (format "^\\[%s\\]\\|.\\[%s:" label label))
- pos)
- (save-excursion
- (save-restriction
- (when (or (re-search-forward re nil t)
- (and (goto-char (point-min))
- (re-search-forward re nil t))
- (and (progn (widen) t)
- (goto-char (point-min))
- (re-search-forward re nil t)))
- (let ((refp (org-footnote-at-reference-p)))
- (cond
- ((and (nth 3 refp) refp))
- ((org-footnote-at-definition-p)))))))))
-
-(defun org-footnote-goto-definition (label)
+(defun org-footnote-goto-definition (label &optional location)
"Move point to the definition of the footnote LABEL.
-Return a non-nil value when a definition has been found."
+
+LOCATION, when non-nil specifies the buffer position of the
+definition.
+
+Throw an error if there is no definition or if it cannot be
+reached from current narrowed part of buffer. Return a non-nil
+value if point was successfully moved."
(interactive "sLabel: ")
- (org-mark-ring-push)
- (let ((def (org-footnote-get-definition label)))
- (if (not def)
- (error "Cannot find definition of footnote %s" label)
- (goto-char (nth 1 def))
- (looking-at (format "\\[%s\\]\\|\\[%s:" label label))
- (goto-char (match-end 0))
- (org-show-context 'link-search)
- (when (derived-mode-p 'org-mode)
- (message "Edit definition and go back with `C-c &' or, if unique, with `C-c C-c'."))
- t)))
+ (let* ((label (org-footnote-normalize-label label))
+ (def-start (or location (nth 1 (org-footnote-get-definition label)))))
+ (cond
+ ((not def-start)
+ (user-error "Cannot find definition of footnote %s" label))
+ ((or (> def-start (point-max)) (< def-start (point-min)))
+ (user-error "Definition is outside narrowed part of buffer")))
+ (org-mark-ring-push)
+ (goto-char def-start)
+ (looking-at (format "\\[fn:%s[]:] ?" (regexp-quote label)))
+ (goto-char (match-end 0))
+ (org-show-context 'link-search)
+ (when (derived-mode-p 'org-mode)
+ (message "%s" (substitute-command-keys
+ "Edit definition and go back with \
+`\\[org-mark-ring-goto]' or, if unique, with `\\[org-ctrl-c-ctrl-c]'.")))
+ t))
(defun org-footnote-goto-previous-reference (label)
"Find the first closest (to point) reference of footnote with label LABEL."
(interactive "sLabel: ")
(org-mark-ring-push)
- (let* ((label (org-footnote-normalize-label label)) ref)
+ (let ((label (org-footnote-normalize-label label))
+ ref)
(save-excursion
(setq ref (or (org-footnote-get-next-reference label t)
(org-footnote-get-next-reference label)
@@ -379,62 +556,74 @@ Return a non-nil value when a definition has been found."
(goto-char (nth 1 ref))
(org-show-context 'link-search))))
+
+;;;; Getters
+
(defun org-footnote-normalize-label (label)
- "Return LABEL as an appropriate string."
- (cond
- ((numberp label) (number-to-string label))
- ((equal "" label) nil)
- ((not (string-match "^[0-9]+$\\|^fn:" label))
- (concat "fn:" label))
- (t label)))
-
-(defun org-footnote-all-labels (&optional with-defs)
- "Return list with all defined foot labels used in the buffer.
-
-If WITH-DEFS is non-nil, also associate the definition to each
-label. The function will then return an alist whose key is label
-and value definition."
- (let* (rtn
- (push-to-rtn
- (function
- ;; Depending on WITH-DEFS, store label or (label . def) of
- ;; footnote reference/definition given as argument in RTN.
- (lambda (el)
- (let ((lbl (car el)))
- (push (if with-defs (cons lbl (nth 3 el)) lbl) rtn))))))
- (save-excursion
- (save-restriction
- (widen)
- ;; Find all labels found in definitions.
- (goto-char (point-min))
- (let (def)
- (while (re-search-forward org-footnote-definition-re nil t)
- (when (setq def (org-footnote-at-definition-p))
- (funcall push-to-rtn def))))
- ;; Find all labels found in references.
- (goto-char (point-min))
- (let (ref)
- (while (setq ref (org-footnote-get-next-reference))
- (goto-char (nth 2 ref))
- (and (car ref) ; ignore anonymous footnotes
- (not (funcall (if with-defs #'assoc #'member) (car ref) rtn))
- (funcall push-to-rtn ref))))))
- rtn))
+ "Return LABEL without \"fn:\" prefix.
+If LABEL is the empty string or constituted of white spaces only,
+return nil instead."
+ (pcase (org-trim label)
+ ("" nil)
+ ((pred (string-prefix-p "fn:")) (substring label 3))
+ (_ label)))
+
+(defun org-footnote-get-definition (label)
+ "Return label, boundaries and definition of the footnote LABEL."
+ (let* ((label (regexp-quote (org-footnote-normalize-label label)))
+ (re (format "^\\[fn:%s\\]\\|.\\[fn:%s:" label label)))
+ (org-with-wide-buffer
+ (goto-char (point-min))
+ (catch 'found
+ (while (re-search-forward re nil t)
+ (let* ((datum (progn (backward-char) (org-element-context)))
+ (type (org-element-type datum)))
+ (when (memq type '(footnote-definition footnote-reference))
+ (throw 'found
+ (list
+ label
+ (org-element-property :begin datum)
+ (org-element-property :end datum)
+ (let ((cbeg (org-element-property :contents-begin datum)))
+ (if (not cbeg) ""
+ (replace-regexp-in-string
+ "[ \t\n]*\\'"
+ ""
+ (buffer-substring-no-properties
+ cbeg
+ (org-element-property :contents-end datum))))))))))
+ nil))))
+
+(defun org-footnote-all-labels ()
+ "List all defined footnote labels used throughout the buffer.
+This function ignores narrowing, if any."
+ (org-with-wide-buffer
+ (goto-char (point-min))
+ (let (all)
+ (while (re-search-forward org-footnote-re nil t)
+ (backward-char)
+ (let ((context (org-element-context)))
+ (when (memq (org-element-type context)
+ '(footnote-definition footnote-reference))
+ (let ((label (org-element-property :label context)))
+ (when label (cl-pushnew label all :test #'equal))))))
+ all)))
(defun org-footnote-unique-label (&optional current)
"Return a new unique footnote label.
-The function returns the first \"fn:N\" or \"N\" label that is
-currently not used.
+The function returns the first numeric label currently unused.
Optional argument CURRENT is the list of labels active in the
buffer."
- (unless current (setq current (org-footnote-all-labels)))
- (let ((fmt (if (eq org-footnote-auto-label 'plain) "%d" "fn:%d"))
- (cnt 1))
- (while (member (format fmt cnt) current)
- (incf cnt))
- (format fmt cnt)))
+ (let ((current (or current (org-footnote-all-labels))))
+ (let ((count 1))
+ (while (member (number-to-string count) current)
+ (cl-incf count))
+ (number-to-string count))))
+
+
+;;;; Adding, Deleting Footnotes
(defun org-footnote-new ()
"Insert a new footnote.
@@ -442,343 +631,66 @@ This command prompts for a label. If this is a label referencing an
existing label, only insert the label. If the footnote label is empty
or new, let the user edit the definition of the footnote."
(interactive)
- (unless (org-footnote-in-valid-context-p)
- (error "Cannot insert a footnote here"))
- (let* ((lbls (and (not (equal org-footnote-auto-label 'random))
- (org-footnote-all-labels)))
- (propose (and (not (equal org-footnote-auto-label 'random))
- (org-footnote-unique-label lbls)))
+ (unless (org-footnote--allow-reference-p)
+ (user-error "Cannot insert a footnote here"))
+ (let* ((all (org-footnote-all-labels))
(label
- (org-footnote-normalize-label
- (cond
- ((member org-footnote-auto-label '(t plain))
- propose)
- ((equal org-footnote-auto-label 'random)
- (require 'org-id)
- (substring (org-id-uuid) 0 8))
- (t
- (org-icompleting-read
- "Label (leave empty for anonymous): "
- (mapcar 'list lbls) nil nil
- (if (eq org-footnote-auto-label 'confirm) propose nil)))))))
- (cond
- ((bolp) (error "Cannot create a footnote reference at left margin"))
- ((not label)
- (insert "[fn:: ]")
- (backward-char 1))
- ((member label lbls)
- (insert "[" label "]")
- (message "New reference to existing note"))
- (org-footnote-define-inline
- (insert "[" label ": ]")
- (backward-char 1)
- (org-footnote-auto-adjust-maybe))
- (t
- (insert "[" label "]")
- (org-footnote-create-definition label)
- (org-footnote-auto-adjust-maybe)))))
-
-(defvar org-blank-before-new-entry) ; silence byte-compiler
+ (if (eq org-footnote-auto-label 'random)
+ (format "%x" (random most-positive-fixnum))
+ (org-footnote-normalize-label
+ (let ((propose (org-footnote-unique-label all)))
+ (if (eq org-footnote-auto-label t) propose
+ (completing-read
+ "Label (leave empty for anonymous): "
+ (mapcar #'list all) nil nil
+ (and (eq org-footnote-auto-label 'confirm) propose))))))))
+ (cond ((not label)
+ (insert "[fn::]")
+ (backward-char 1))
+ ((member label all)
+ (insert "[fn:" label "]")
+ (message "New reference to existing note"))
+ (org-footnote-define-inline
+ (insert "[fn:" label ":]")
+ (backward-char 1)
+ (org-footnote-auto-adjust-maybe))
+ (t
+ (insert "[fn:" label "]")
+ (let ((p (org-footnote-create-definition label)))
+ ;; `org-footnote-goto-definition' needs to be called
+ ;; after `org-footnote-auto-adjust-maybe'. Otherwise
+ ;; both label and location of the definition are lost.
+ ;; On the contrary, it needs to be called before
+ ;; `org-edit-footnote-reference' so that the remote
+ ;; editing buffer can display the correct label.
+ (if (ignore-errors (org-footnote-goto-definition label p))
+ (org-footnote-auto-adjust-maybe)
+ ;; Definition was created outside current scope: edit
+ ;; it remotely.
+ (org-footnote-auto-adjust-maybe)
+ (org-edit-footnote-reference)))))))
+
(defun org-footnote-create-definition (label)
- "Start the definition of a footnote with label LABEL."
- (interactive "sLabel: ")
+ "Start the definition of a footnote with label LABEL.
+Return buffer position at the beginning of the definition. This
+function doesn't move point."
(let ((label (org-footnote-normalize-label label))
- electric-indent-mode) ;; Prevent wrong indentation
- (cond
- ;; In an Org file.
- ((derived-mode-p 'org-mode)
- ;; If `org-footnote-section' is defined, find it, or create it
- ;; at the end of the buffer.
- (when org-footnote-section
- (goto-char (point-min))
- (let ((re (concat "^\\*+[ \t]+" org-footnote-section "[ \t]*$")))
- (unless (or (re-search-forward re nil t)
- (and (progn (widen) t)
- (re-search-forward re nil t)))
- (goto-char (point-max))
- (skip-chars-backward " \t\r\n")
- (unless (bolp) (newline))
- ;; Insert new section. Separate it from the previous one
- ;; with a blank line, unless `org-blank-before-new-entry'
- ;; explicitly says no.
- (when (and (cdr (assq 'heading org-blank-before-new-entry))
- (zerop (save-excursion (org-back-over-empty-lines))))
- (insert "\n"))
- (insert "* " org-footnote-section "\n"))))
- ;; Move to the end of this entry (which may be
- ;; `org-footnote-section' or the current one).
- (org-footnote-goto-local-insertion-point)
- (org-show-context 'link-search))
- (t
- ;; In a non-Org file. Search for footnote tag, or create it if
- ;; specified (at the end of buffer, or before signature if in
- ;; Message mode). Set point after any definition already there.
- (let ((tag (and org-footnote-tag-for-non-org-mode-files
- (concat "^" (regexp-quote
- org-footnote-tag-for-non-org-mode-files)
- "[ \t]*$")))
- (max (if (and (derived-mode-p 'message-mode)
- (goto-char (point-max))
- (re-search-backward
- message-signature-separator nil t))
- (progn
- ;; Ensure one blank line separates last
- ;; footnote from signature.
- (beginning-of-line)
- (open-line 2)
- (point-marker))
- (point-max-marker))))
- (set-marker-insertion-type max t)
- (goto-char max)
- ;; Check if the footnote tag is defined but missing. In this
- ;; case, insert it, before any footnote or one blank line
- ;; after any previous text.
- (when (and tag (not (re-search-backward tag nil t)))
- (skip-chars-backward " \t\r\n")
- (while (re-search-backward org-footnote-definition-re nil t))
- (unless (bolp) (newline 2))
- (insert org-footnote-tag-for-non-org-mode-files "\n\n"))
- ;; Remove superfluous white space and clear marker.
- (goto-char max)
- (skip-chars-backward " \t\r\n")
- (delete-region (point) max)
- (unless (bolp) (newline))
- (set-marker max nil))))
- ;; Insert footnote label.
- (when (zerop (org-back-over-empty-lines)) (newline))
- (insert "[" label "] \n")
- (backward-char)
- ;; Only notify user about next possible action when in an Org
- ;; buffer, as the bindings may have different meanings otherwise.
- (when (derived-mode-p 'org-mode)
- (message
- "Edit definition and go back with `C-c &' or, if unique, with `C-c C-c'."))))
-
-;;;###autoload
-(defun org-footnote-action (&optional special)
- "Do the right thing for footnotes.
-
-When at a footnote reference, jump to the definition.
-
-When at a definition, jump to the references if they exist, offer
-to create them otherwise.
-
-When neither at definition or reference, create a new footnote,
-interactively.
-
-With prefix arg SPECIAL, offer additional commands in a menu."
- (interactive "P")
- (let (tmp c)
- (cond
- (special
- (message "Footnotes: [s]ort | [r]enumber fn:N | [S]=r+s |->[n]umeric | [d]elete")
- (setq c (read-char-exclusive))
- (cond
- ((eq c ?s) (org-footnote-normalize 'sort))
- ((eq c ?r) (org-footnote-renumber-fn:N))
- ((eq c ?S)
- (org-footnote-renumber-fn:N)
- (org-footnote-normalize 'sort))
- ((eq c ?n) (org-footnote-normalize))
- ((eq c ?d) (org-footnote-delete))
- (t (error "No such footnote command %c" c))))
- ((setq tmp (org-footnote-at-reference-p))
- (cond
- ;; Anonymous footnote: move point at the beginning of its
- ;; definition.
- ((not (car tmp))
- (goto-char (nth 1 tmp))
- (forward-char 5))
- ;; A definition exists: move to it.
- ((ignore-errors (org-footnote-goto-definition (car tmp))))
- ;; No definition exists: offer to create it.
- ((yes-or-no-p (format "No definition for %s. Create one? " (car tmp)))
- (org-footnote-create-definition (car tmp)))))
- ((setq tmp (org-footnote-at-definition-p))
- (org-footnote-goto-previous-reference (car tmp)))
- (t (org-footnote-new)))))
-
-;;;###autoload
-(defun org-footnote-normalize (&optional sort-only)
- "Collect the footnotes in various formats and normalize them.
-
-This finds the different sorts of footnotes allowed in Org, and
-normalizes them to the usual [N] format.
-
-When SORT-ONLY is set, only sort the footnote definitions into the
-referenced sequence."
- ;; This is based on Paul's function, but rewritten.
- ;;
- ;; Re-create `org-with-limited-levels', but not limited to Org
- ;; buffers.
- (let* ((limit-level
- (and (boundp 'org-inlinetask-min-level)
- org-inlinetask-min-level
- (1- org-inlinetask-min-level)))
- (nstars (and limit-level
- (if org-odd-levels-only (1- (* limit-level 2))
- limit-level)))
- (org-outline-regexp
- (concat "\\*" (if nstars (format "\\{1,%d\\} " nstars) "+ ")))
- (count 0)
- ins-point ref ref-table)
- (save-excursion
- ;; 1. Find every footnote reference, extract the definition, and
- ;; collect that data in REF-TABLE. If SORT-ONLY is nil, also
- ;; normalize references.
- (goto-char (point-min))
- (while (setq ref (org-footnote-get-next-reference))
- (let* ((lbl (car ref))
- (pos (nth 1 ref))
- ;; When footnote isn't anonymous, check if it's label
- ;; (REF) is already stored in REF-TABLE. In that case,
- ;; extract number used to identify it (MARKER). If
- ;; footnote is unknown, increment the global counter
- ;; (COUNT) to create an unused identifier.
- (a (and lbl (assoc lbl ref-table)))
- (marker (or (nth 1 a) (incf count)))
- ;; Is the reference inline or pointing to an inline
- ;; footnote?
- (inlinep (or (stringp (nth 3 ref)) (nth 3 a))))
- ;; Replace footnote reference with [MARKER]. Maybe fill
- ;; paragraph once done. If SORT-ONLY is non-nil, only move
- ;; to the end of reference found to avoid matching it twice.
- (if sort-only (goto-char (nth 2 ref))
- (delete-region (nth 1 ref) (nth 2 ref))
- (goto-char (nth 1 ref))
- (insert (format "[%d]" marker))
- (and inlinep
- org-footnote-fill-after-inline-note-extraction
- (org-fill-paragraph)))
- ;; Add label (REF), identifier (MARKER), definition (DEF)
- ;; type (INLINEP) and position (POS) to REF-TABLE if data
- ;; was unknown.
- (unless a
- (let ((def (or (nth 3 ref) ; Inline definition.
- (nth 3 (org-footnote-get-definition lbl)))))
- (push (list lbl marker def
- ;; Reference beginning position is a marker
- ;; to preserve it during further buffer
- ;; modifications.
- inlinep (copy-marker pos)) ref-table)))))
- ;; 2. Find and remove the footnote section, if any. Also
- ;; determine where footnotes shall be inserted (INS-POINT).
- (cond
- ((and org-footnote-section (derived-mode-p 'org-mode))
- (goto-char (point-min))
- (if (re-search-forward
- (concat "^\\*[ \t]+" (regexp-quote org-footnote-section)
- "[ \t]*$") nil t)
- (delete-region (match-beginning 0) (org-end-of-subtree t t)))
- ;; A new footnote section is inserted by default at the end of
- ;; the buffer.
- (goto-char (point-max))
- (skip-chars-backward " \r\t\n")
- (forward-line)
- (unless (bolp) (newline)))
- ;; No footnote section set: Footnotes will be added at the end
- ;; of the section containing their first reference.
- ((derived-mode-p 'org-mode))
- (t
- ;; Remove any left-over tag in the buffer, if one is set up.
- (when org-footnote-tag-for-non-org-mode-files
- (let ((tag (concat "^" (regexp-quote
- org-footnote-tag-for-non-org-mode-files)
- "[ \t]*$")))
- (goto-char (point-min))
- (while (re-search-forward tag nil t)
- (replace-match "")
- (delete-region (point) (progn (forward-line) (point))))))
- ;; In Message mode, ensure footnotes are inserted before the
- ;; signature.
- (if (and (derived-mode-p 'message-mode)
- (goto-char (point-max))
- (re-search-backward message-signature-separator nil t))
- (beginning-of-line)
- (goto-char (point-max)))))
- (setq ins-point (point-marker))
- ;; 3. Clean-up REF-TABLE.
- (setq ref-table
- (delq nil
- (mapcar
- (lambda (x)
- (cond
- ;; When only sorting, ignore inline footnotes.
- ;; Also clear position marker.
- ((and sort-only (nth 3 x))
- (set-marker (nth 4 x) nil) nil)
- ;; No definition available: provide one.
- ((not (nth 2 x))
- (append
- (list (car x) (nth 1 x)
- (format "DEFINITION NOT FOUND: %s" (car x)))
- (nthcdr 3 x)))
- (t x)))
- ref-table)))
- (setq ref-table (nreverse ref-table))
- ;; 4. Remove left-over definitions in the buffer.
- (mapc (lambda (x)
- (unless (nth 3 x) (org-footnote-delete-definitions (car x))))
- ref-table)
- ;; 5. Insert the footnotes again in the buffer, at the
- ;; appropriate spot.
- (goto-char ins-point)
- (cond
- ;; No footnote: exit.
- ((not ref-table))
- ;; Cases when footnotes should be inserted in one place.
- ((or (not (derived-mode-p 'org-mode)) org-footnote-section)
- ;; Insert again the section title, if any. Ensure that title,
- ;; or the subsequent footnotes, will be separated by a blank
- ;; lines from the rest of the document. In an Org buffer,
- ;; separate section with a blank line, unless explicitly
- ;; stated in `org-blank-before-new-entry'.
- (if (not (derived-mode-p 'org-mode))
- (progn (skip-chars-backward " \t\n\r")
- (delete-region (point) ins-point)
- (unless (bolp) (newline))
- (when org-footnote-tag-for-non-org-mode-files
- (insert "\n" org-footnote-tag-for-non-org-mode-files "\n")))
- (when (and (cdr (assq 'heading org-blank-before-new-entry))
- (zerop (save-excursion (org-back-over-empty-lines))))
- (insert "\n"))
- (insert "* " org-footnote-section "\n"))
- (set-marker ins-point nil)
- ;; Insert the footnotes, separated by a blank line.
- (insert
- (mapconcat
- (lambda (x)
- ;; Clean markers.
- (set-marker (nth 4 x) nil)
- (format "\n[%s] %s" (nth (if sort-only 0 1) x) (nth 2 x)))
- ref-table "\n"))
- (unless (eobp) (insert "\n\n")))
- ;; Each footnote definition has to be inserted at the end of
- ;; the section where its first reference belongs.
- (t
- (mapc
- (lambda (x)
- (let ((pos (nth 4 x)))
- (goto-char pos)
- ;; Clean marker.
- (set-marker pos nil))
- (org-footnote-goto-local-insertion-point)
- (insert (format "\n[%s] %s\n"
- (if sort-only (car x) (nth 1 x))
- (nth 2 x))))
- ref-table))))))
-
-(defun org-footnote-goto-local-insertion-point ()
- "Find insertion point for footnote, just before next outline heading."
- (org-with-limited-levels (outline-next-heading))
- (or (bolp) (newline))
- (beginning-of-line 0)
- (while (and (not (bobp)) (= (char-after) ?#))
- (beginning-of-line 0))
- (if (let ((case-fold-search t)) (looking-at "[ \t]*#\\+tblfm:")) (beginning-of-line 2))
- (end-of-line 1)
- (skip-chars-backward "\n\r\t ")
- (forward-line))
+ electric-indent-mode) ; Prevent wrong indentation.
+ (org-with-wide-buffer
+ (cond
+ ((not org-footnote-section) (org-footnote--goto-local-insertion-point))
+ ((save-excursion
+ (goto-char (point-min))
+ (re-search-forward
+ (concat "^\\*+[ \t]+" (regexp-quote org-footnote-section) "[ \t]*$")
+ nil t))
+ (goto-char (match-end 0))
+ (forward-line)
+ (unless (bolp) (insert "\n")))
+ (t (org-footnote--clear-footnote-section)))
+ (when (zerop (org-back-over-empty-lines)) (insert "\n"))
+ (insert "[fn:" label "] \n")
+ (line-beginning-position 0))))
(defun org-footnote-delete-references (label)
"Delete every reference to footnote LABEL.
@@ -789,7 +701,7 @@ Return the number of footnotes removed."
(while (setq ref (org-footnote-get-next-reference label))
(goto-char (nth 1 ref))
(delete-region (nth 1 ref) (nth 2 ref))
- (incf nref))
+ (cl-incf nref))
nref)))
(defun org-footnote-delete-definitions (label)
@@ -797,17 +709,21 @@ Return the number of footnotes removed."
Return the number of footnotes removed."
(save-excursion
(goto-char (point-min))
- (let ((def-re (concat "^\\[" (regexp-quote label) "\\]"))
+ (let ((def-re (format "^\\[fn:%s\\]" (regexp-quote label)))
(ndef 0))
(while (re-search-forward def-re nil t)
- (let ((full-def (org-footnote-at-definition-p)))
- (when full-def
- ;; Remove the footnote, and all blank lines before it.
- (goto-char (nth 1 full-def))
- (skip-chars-backward " \r\t\n")
- (unless (bolp) (forward-line))
- (delete-region (point) (nth 2 full-def))
- (incf ndef))))
+ (pcase (org-footnote-at-definition-p)
+ (`(,_ ,start ,end ,_)
+ ;; Remove the footnote, and all blank lines before it.
+ (delete-region (progn
+ (goto-char start)
+ (skip-chars-backward " \r\t\n")
+ (if (bobp) (point) (line-beginning-position 2)))
+ (progn
+ (goto-char end)
+ (skip-chars-backward " \r\t\n")
+ (if (bobp) (point) (line-beginning-position 2))))
+ (cl-incf ndef))))
ndef)))
(defun org-footnote-delete (&optional label)
@@ -843,24 +759,165 @@ If LABEL is non-nil, delete that footnote instead."
(message "%d definition(s) of and %d reference(s) of footnote %s removed"
ndef nref label))))
+
+;;;; Sorting, Renumbering, Normalizing
+
(defun org-footnote-renumber-fn:N ()
- "Renumber the simple footnotes like fn:17 into a sequence in the document."
+ "Order numbered footnotes into a sequence in the document."
(interactive)
- (let (map (n 0))
- (org-with-wide-buffer
- (goto-char (point-min))
- (while (re-search-forward "\\[fn:\\([0-9]+\\)[]:]" nil t)
- (save-excursion
- (goto-char (match-beginning 0))
- ;; Ensure match is a footnote reference or definition.
- (when (save-match-data (if (bolp)
- (org-footnote-at-definition-p)
- (org-footnote-at-reference-p)))
- (let ((new-val (or (cdr (assoc (match-string 1) map))
- (number-to-string (incf n)))))
- (unless (assoc (match-string 1) map)
- (push (cons (match-string 1) new-val) map))
- (replace-match new-val nil nil nil 1))))))))
+ (let ((references (org-footnote--collect-references)))
+ (unwind-protect
+ (let* ((c 0)
+ (references (cl-remove-if-not
+ (lambda (r) (string-match-p "\\`[0-9]+\\'" (car r)))
+ references))
+ (alist (mapcar (lambda (l) (cons l (number-to-string (cl-incf c))))
+ (delete-dups (mapcar #'car references)))))
+ (org-with-wide-buffer
+ ;; Re-number references.
+ (dolist (ref references)
+ (goto-char (nth 1 ref))
+ (org-footnote--set-label (cdr (assoc (nth 0 ref) alist))))
+ ;; Re-number definitions.
+ (goto-char (point-min))
+ (while (re-search-forward "^\\[fn:\\([0-9]+\\)\\]" nil t)
+ (replace-match (or (cdr (assoc (match-string 1) alist))
+ ;; Un-referenced definitions get
+ ;; higher numbers.
+ (number-to-string (cl-incf c)))
+ nil nil nil 1))))
+ (dolist (r references) (set-marker (nth 1 r) nil)))))
+
+(defun org-footnote-sort ()
+ "Rearrange footnote definitions in the current buffer.
+Sort footnote definitions so they match order of footnote
+references. Also relocate definitions at the end of their
+relative section or within a single footnote section, according
+to `org-footnote-section'. Inline definitions are ignored."
+ (let ((references (org-footnote--collect-references)))
+ (unwind-protect
+ (let ((definitions (org-footnote--collect-definitions 'delete)))
+ (org-with-wide-buffer
+ (org-footnote--clear-footnote-section)
+ ;; Insert footnote definitions at the appropriate location,
+ ;; separated by a blank line. Each definition is inserted
+ ;; only once throughout the buffer.
+ (let (inserted)
+ (dolist (cell references)
+ (let ((label (car cell))
+ (nested (not (nth 2 cell)))
+ (inline (nth 3 cell)))
+ (unless (or (member label inserted) inline)
+ (push label inserted)
+ (unless (or org-footnote-section nested)
+ ;; If `org-footnote-section' is non-nil, or
+ ;; reference is nested, point is already at the
+ ;; correct position. Otherwise, move at the
+ ;; appropriate location within the section
+ ;; containing the reference.
+ (goto-char (nth 1 cell))
+ (org-footnote--goto-local-insertion-point))
+ (insert "\n"
+ (or (cdr (assoc label definitions))
+ (format "[fn:%s] DEFINITION NOT FOUND." label))
+ "\n"))))
+ ;; Insert un-referenced footnote definitions at the end.
+ (let ((unreferenced
+ (cl-remove-if (lambda (d) (member (car d) inserted))
+ definitions)))
+ (dolist (d unreferenced) (insert "\n" (cdr d) "\n"))))))
+ ;; Clear dangling markers in the buffer.
+ (dolist (r references) (set-marker (nth 1 r) nil)))))
+
+(defun org-footnote-normalize ()
+ "Turn every footnote in buffer into a numbered one."
+ (interactive)
+ (let ((references (org-footnote--collect-references 'anonymous)))
+ (unwind-protect
+ (let ((n 0)
+ (translations nil)
+ (definitions nil))
+ (org-with-wide-buffer
+ ;; Update label for reference. We need to do this before
+ ;; clearing definitions in order to rename nested footnotes
+ ;; before they are deleted.
+ (dolist (cell references)
+ (let* ((label (car cell))
+ (anonymous (not label))
+ (new
+ (cond
+ ;; In order to differentiate anonymous
+ ;; references from regular ones, set their
+ ;; labels to integers, not strings.
+ (anonymous (setcar cell (cl-incf n)))
+ ((cdr (assoc label translations)))
+ (t (let ((l (number-to-string (cl-incf n))))
+ (push (cons label l) translations)
+ l)))))
+ (goto-char (nth 1 cell)) ; Move to reference's start.
+ (org-footnote--set-label
+ (if anonymous (number-to-string new) new))
+ (let ((size (nth 3 cell)))
+ ;; Transform inline footnotes into regular references
+ ;; and retain their definition for later insertion as
+ ;; a regular footnote definition.
+ (when size
+ (let ((def (concat
+ (format "[fn:%s] " new)
+ (org-trim
+ (substring
+ (delete-and-extract-region
+ (point) (+ (point) size 1))
+ 1)))))
+ (push (cons (if anonymous new label) def) definitions)
+ (when org-footnote-fill-after-inline-note-extraction
+ (org-fill-paragraph)))))))
+ ;; Collect definitions. Update labels according to ALIST.
+ (let ((definitions
+ (nconc definitions
+ (org-footnote--collect-definitions 'delete)))
+ (inserted))
+ (org-footnote--clear-footnote-section)
+ (dolist (cell references)
+ (let* ((label (car cell))
+ (anonymous (integerp label))
+ (pos (nth 1 cell)))
+ ;; Move to appropriate location, if required. When
+ ;; there is a footnote section or reference is
+ ;; nested, point is already at the expected location.
+ (unless (or org-footnote-section (not (nth 2 cell)))
+ (goto-char pos)
+ (org-footnote--goto-local-insertion-point))
+ ;; Insert new definition once label is updated.
+ (unless (member label inserted)
+ (push label inserted)
+ (let ((stored (cdr (assoc label definitions)))
+ ;; Anonymous footnotes' label is already
+ ;; up-to-date.
+ (new (if anonymous label
+ (cdr (assoc label translations)))))
+ (insert "\n"
+ (cond
+ ((not stored)
+ (format "[fn:%s] DEFINITION NOT FOUND." new))
+ (anonymous stored)
+ (t
+ (replace-regexp-in-string
+ "\\`\\[fn:\\(.*?\\)\\]" new stored nil nil 1)))
+ "\n")))))
+ ;; Insert un-referenced footnote definitions at the end.
+ (let ((unreferenced
+ (cl-remove-if (lambda (d) (member (car d) inserted))
+ definitions)))
+ (dolist (d unreferenced)
+ (insert "\n"
+ (replace-regexp-in-string
+ org-footnote-definition-re
+ (format "[fn:%d]" (cl-incf n))
+ (cdr d))
+ "\n"))))))
+ ;; Clear dangling markers.
+ (dolist (r references) (set-marker (nth 1 r) nil)))))
(defun org-footnote-auto-adjust-maybe ()
"Renumber and/or sort footnotes according to user settings."
@@ -868,14 +925,77 @@ If LABEL is non-nil, delete that footnote instead."
(org-footnote-renumber-fn:N))
(when (memq org-footnote-auto-adjust '(t sort))
(let ((label (car (org-footnote-at-definition-p))))
- (org-footnote-normalize 'sort)
+ (org-footnote-sort)
(when label
(goto-char (point-min))
- (and (re-search-forward (concat "^\\[" (regexp-quote label) "\\]")
+ (and (re-search-forward (format "^\\[fn:%s\\]" (regexp-quote label))
nil t)
(progn (insert " ")
(just-one-space)))))))
+
+;;;; End-user interface
+
+;;;###autoload
+(defun org-footnote-action (&optional special)
+ "Do the right thing for footnotes.
+
+When at a footnote reference, jump to the definition.
+
+When at a definition, jump to the references if they exist, offer
+to create them otherwise.
+
+When neither at definition or reference, create a new footnote,
+interactively if possible.
+
+With prefix arg SPECIAL, or when no footnote can be created,
+offer additional commands in a menu."
+ (interactive "P")
+ (let* ((context (and (not special) (org-element-context)))
+ (type (org-element-type context)))
+ (cond
+ ;; On white space after element, insert a new footnote.
+ ((and context
+ (> (point)
+ (save-excursion
+ (goto-char (org-element-property :end context))
+ (skip-chars-backward " \t")
+ (point))))
+ (org-footnote-new))
+ ((eq type 'footnote-reference)
+ (let ((label (org-element-property :label context)))
+ (cond
+ ;; Anonymous footnote: move point at the beginning of its
+ ;; definition.
+ ((not label)
+ (goto-char (org-element-property :contents-begin context)))
+ ;; Check if a definition exists: then move to it.
+ ((let ((p (nth 1 (org-footnote-get-definition label))))
+ (when p (org-footnote-goto-definition label p))))
+ ;; No definition exists: offer to create it.
+ ((yes-or-no-p (format "No definition for %s. Create one? " label))
+ (let ((p (org-footnote-create-definition label)))
+ (or (ignore-errors (org-footnote-goto-definition label p))
+ ;; Since definition was created outside current scope,
+ ;; edit it remotely.
+ (org-edit-footnote-reference)))))))
+ ((eq type 'footnote-definition)
+ (org-footnote-goto-previous-reference
+ (org-element-property :label context)))
+ ((or special (not (org-footnote--allow-reference-p)))
+ (message "Footnotes: [s]ort | [r]enumber fn:N | [S]=r+s | [n]ormalize | \
+\[d]elete")
+ (pcase (read-char-exclusive)
+ (?s (org-footnote-sort))
+ (?r (org-footnote-renumber-fn:N))
+ (?S (org-footnote-renumber-fn:N)
+ (org-footnote-sort))
+ (?n (org-footnote-normalize))
+ (?d (org-footnote-delete))
+ (char (error "No such footnote command %c" char))))
+ (t (org-footnote-new)))))
+
+
(provide 'org-footnote)
;; Local variables:
diff --git a/lisp/org/org-gnus.el b/lisp/org/org-gnus.el
index 1d287a740b5..b9d098957c8 100644
--- a/lisp/org/org-gnus.el
+++ b/lisp/org/org-gnus.el
@@ -1,4 +1,4 @@
-;;; org-gnus.el --- Support for links to Gnus groups and messages from within Org-mode
+;;; org-gnus.el --- Support for Links to Gnus Groups and Messages -*- lexical-binding: t; -*-
;; Copyright (C) 2004-2017 Free Software Foundation, Inc.
@@ -25,8 +25,8 @@
;;
;;; Commentary:
-;; This file implements links to Gnus groups and messages from within Org-mode.
-;; Org-mode loads this module by default - if this is not what you want,
+;; This file implements links to Gnus groups and messages from within Org.
+;; Org mode loads this module by default - if this is not what you want,
;; configure the variable `org-modules'.
;;; Code:
@@ -36,18 +36,20 @@
(eval-when-compile (require 'gnus-sum))
;; Declare external functions and variables
+
(declare-function message-fetch-field "message" (header &optional not-all))
(declare-function message-narrow-to-head-1 "message" nil)
-;; The following line suppresses a compiler warning stemming from gnus-sum.el
(declare-function gnus-summary-last-subject "gnus-sum" nil)
+(declare-function nnvirtual-map-article "nnvirtual" (article))
+
;; Customization variables
-(org-defvaralias 'org-usenet-links-prefer-google 'org-gnus-prefer-web-links)
+(defvaralias 'org-usenet-links-prefer-google 'org-gnus-prefer-web-links)
(defcustom org-gnus-prefer-web-links nil
"If non-nil, `org-store-link' creates web links to Google groups or Gmane.
-When nil, Gnus will be used for such links.
-Using a prefix arg to the command \\[org-store-link] (`org-store-link')
+\\When nil, Gnus will be used for such links.
+Using a prefix argument to the command `\\[org-store-link]' (`org-store-link')
negates this setting for the duration of the command."
:group 'org-link-store
:type 'boolean)
@@ -72,20 +74,21 @@ this variable to t."
:type 'boolean)
;; Install the link type
-(org-add-link-type "gnus" 'org-gnus-open)
-(add-hook 'org-store-link-functions 'org-gnus-store-link)
+(org-link-set-parameters "gnus" :follow #'org-gnus-open :store #'org-gnus-store-link)
;; Implementation
-;; FIXME: nnimap-group-overview-filename was removed from Gnus in
-;; September 2010. Perhaps remove this function?
(defun org-gnus-nnimap-cached-article-number (group server message-id)
"Return cached article number (uid) of message in GROUP on SERVER.
MESSAGE-ID is the message-id header field that identifies the
message. If the uid is not cached, return nil."
(with-temp-buffer
- (let ((nov (nnimap-group-overview-filename group server)))
- (when (file-exists-p nov)
+ (let ((nov (and (fboundp 'nnimap-group-overview-filename)
+ ;; nnimap-group-overview-filename was removed from
+ ;; Gnus in September 2010, and therefore should
+ ;; only be present in Emacs 23.1.
+ (nnimap-group-overview-filename group server))))
+ (when (and nov (file-exists-p nov))
(mm-insert-file-contents nov)
(set-buffer-modified-p nil)
(goto-char (point-min))
@@ -104,7 +107,7 @@ Otherwise create a link to the group inside Gnus.
If `org-store-link' was called with a prefix arg the meaning of
`org-gnus-prefer-web-links' is reversed."
(let ((unprefixed-group (replace-regexp-in-string "^[^:]+:" "" group)))
- (if (and (string-match "^nntp" group) ;; Only for nntp groups
+ (if (and (string-prefix-p "nntp" group) ;; Only for nntp groups
(org-xor current-prefix-arg
org-gnus-prefer-web-links))
(concat (if (string-match "gmane" unprefixed-group)
@@ -156,21 +159,17 @@ If `org-store-link' was called with a prefix arg the meaning of
(header (with-current-buffer gnus-summary-buffer
(gnus-summary-article-header)))
(from (mail-header-from header))
- (message-id (org-remove-angle-brackets (mail-header-id header)))
+ (message-id (org-unbracket-string "<" ">" (mail-header-id header)))
(date (org-trim (mail-header-date header)))
- (date-ts (and date
- (ignore-errors
- (format-time-string
- (org-time-stamp-format t)
- (date-to-time date)))))
- (date-ts-ia (and date
- (ignore-errors
- (format-time-string
- (org-time-stamp-format t t)
- (date-to-time date)))))
(subject (copy-sequence (mail-header-subject header)))
(to (cdr (assq 'To (mail-header-extra header))))
newsgroups x-no-archive desc link)
+ (cl-case (car (gnus-find-method-for-group gnus-newsgroup-name))
+ (nnvirtual
+ (setq group (car (nnvirtual-map-article
+ (gnus-summary-article-number)))))
+ (nnir
+ (setq group (nnir-article-group (gnus-summary-article-number)))))
;; Remove text properties of subject string to avoid Emacs bug
;; #3506
(set-text-properties 0 (length subject) nil subject)
@@ -183,11 +182,8 @@ If `org-store-link' was called with a prefix arg the meaning of
(setq to (or to (gnus-fetch-original-field "To"))
newsgroups (gnus-fetch-original-field "Newsgroups")
x-no-archive (gnus-fetch-original-field "x-no-archive")))
- (org-store-link-props :type "gnus" :from from :subject subject
+ (org-store-link-props :type "gnus" :from from :date date :subject subject
:message-id message-id :group group :to to)
- (when date
- (org-add-link-props :date date :date-timestamp date-ts
- :date-timestamp-inactive date-ts-ia))
(setq desc (org-email-link-description)
link (org-gnus-article-link
group newsgroups message-id x-no-archive))
@@ -206,7 +202,7 @@ If `org-store-link' was called with a prefix arg the meaning of
(let ((gcc (car (last
(message-unquote-tokens
(message-tokenize-header (mail-fetch-field "gcc" nil t) " ,")))))
- (id (org-remove-angle-brackets (mail-fetch-field "Message-ID")))
+ (id (org-unbracket-string "<" ">" (mail-fetch-field "Message-ID")))
(to (mail-fetch-field "To"))
(from (mail-fetch-field "From"))
(subject (mail-fetch-field "Subject"))
@@ -250,10 +246,8 @@ If `org-store-link' was called with a prefix arg the meaning of
(require 'gnus)
(funcall (cdr (assq 'gnus org-link-frame-setup)))
(if gnus-other-frame-object (select-frame gnus-other-frame-object))
- (when group
- (setq group (org-no-properties group)))
- (when article
- (setq article (org-no-properties article)))
+ (setq group (org-no-properties group))
+ (setq article (org-no-properties article))
(cond ((and group article)
(gnus-activate-group group)
(condition-case nil
diff --git a/lisp/org/org-habit.el b/lisp/org/org-habit.el
index bbbf845d148..1f61565719f 100644
--- a/lisp/org/org-habit.el
+++ b/lisp/org/org-habit.el
@@ -1,4 +1,4 @@
-;;; org-habit.el --- The habit tracking code for Org-mode
+;;; org-habit.el --- The habit tracking code for Org -*- lexical-binding: t; -*-
;; Copyright (C) 2009-2017 Free Software Foundation, Inc.
@@ -24,18 +24,16 @@
;;
;;; Commentary:
-;; This file contains the habit tracking code for Org-mode
+;; This file contains the habit tracking code for Org mode
;;; Code:
+(require 'cl-lib)
(require 'org)
(require 'org-agenda)
-(eval-when-compile
- (require 'cl))
-
(defgroup org-habit nil
- "Options concerning habit tracking in Org-mode."
+ "Options concerning habit tracking in Org mode."
:tag "Org Habit"
:group 'org-progress)
@@ -165,16 +163,17 @@ Returns a list with the following elements:
2: Optional deadline (nil if not present)
3: If deadline, the repeater for the deadline, otherwise nil
4: A list of all the past dates this todo was mark closed
+ 5: Repeater type as a string
This list represents a \"habit\" for the rest of this module."
(save-excursion
(if pom (goto-char pom))
- (assert (org-is-habit-p (point)))
+ (cl-assert (org-is-habit-p (point)))
(let* ((scheduled (org-get-scheduled-time (point)))
(scheduled-repeat (org-get-repeat org-scheduled-string))
(end (org-entry-end-position))
(habit-entry (org-no-properties (nth 4 (org-heading-components))))
- closed-dates deadline dr-days sr-days)
+ closed-dates deadline dr-days sr-days sr-type)
(if scheduled
(setq scheduled (time-to-days scheduled))
(error "Habit %s has no scheduled date" habit-entry))
@@ -182,7 +181,9 @@ This list represents a \"habit\" for the rest of this module."
(error
"Habit `%s' has no scheduled repeat period or has an incorrect one"
habit-entry))
- (setq sr-days (org-habit-duration-to-days scheduled-repeat))
+ (setq sr-days (org-habit-duration-to-days scheduled-repeat)
+ sr-type (progn (string-match "[\\.+]?\\+" scheduled-repeat)
+ (match-string-no-properties 0 scheduled-repeat)))
(unless (> sr-days 0)
(error "Habit %s scheduled repeat period is less than 1d" habit-entry))
(when (string-match "/\\([0-9]+[dwmy]\\)" scheduled-repeat)
@@ -197,17 +198,33 @@ This list represents a \"habit\" for the rest of this module."
(reversed org-log-states-order-reversed)
(search (if reversed 're-search-forward 're-search-backward))
(limit (if reversed end (point)))
- (count 0))
+ (count 0)
+ (re (format
+ "^[ \t]*-[ \t]+\\(?:State \"%s\".*%s%s\\)"
+ (regexp-opt org-done-keywords)
+ org-ts-regexp-inactive
+ (let ((value (cdr (assq 'done org-log-note-headings))))
+ (if (not value) ""
+ (concat "\\|"
+ (org-replace-escapes
+ (regexp-quote value)
+ `(("%d" . ,org-ts-regexp-inactive)
+ ("%D" . ,org-ts-regexp)
+ ("%s" . "\"\\S-+\"")
+ ("%S" . "\"\\S-+\"")
+ ("%t" . ,org-ts-regexp-inactive)
+ ("%T" . ,org-ts-regexp)
+ ("%u" . ".*?")
+ ("%U" . ".*?")))))))))
(unless reversed (goto-char end))
- (while (and (< count maxdays)
- (funcall search (format "- State \"%s\".*\\[\\([^]]+\\)\\]"
- (regexp-opt org-done-keywords))
- limit t))
+ (while (and (< count maxdays) (funcall search re limit t))
(push (time-to-days
- (org-time-string-to-time (match-string-no-properties 1)))
+ (org-time-string-to-time
+ (or (match-string-no-properties 1)
+ (match-string-no-properties 2))))
closed-dates)
(setq count (1+ count))))
- (list scheduled sr-days deadline dr-days closed-dates))))
+ (list scheduled sr-days deadline dr-days closed-dates sr-type))))
(defsubst org-habit-scheduled (habit)
(nth 0 habit))
@@ -225,6 +242,8 @@ This list represents a \"habit\" for the rest of this module."
(org-habit-scheduled-repeat habit)))
(defsubst org-habit-done-dates (habit)
(nth 4 habit))
+(defsubst org-habit-repeat-type (habit)
+ (nth 5 habit))
(defsubst org-habit-get-priority (habit &optional moment)
"Determine the relative priority of a habit.
@@ -265,7 +284,6 @@ Habits are assigned colors on the following basis:
schedule's repeat period."
(let* ((scheduled (or scheduled-days (org-habit-scheduled habit)))
(s-repeat (org-habit-scheduled-repeat habit))
- (scheduled-end (+ scheduled (1- s-repeat)))
(d-repeat (org-habit-deadline-repeat habit))
(deadline (if scheduled-days
(+ scheduled-days (- d-repeat s-repeat))
@@ -289,13 +307,14 @@ Habits are assigned colors on the following basis:
CURRENT gives the current time between STARTING and ENDING, for
the purpose of drawing the graph. It need not be the actual
current time."
- (let* ((done-dates (sort (org-habit-done-dates habit) '<))
+ (let* ((all-done-dates (sort (org-habit-done-dates habit) #'<))
+ (done-dates all-done-dates)
(scheduled (org-habit-scheduled habit))
(s-repeat (org-habit-scheduled-repeat habit))
(start (time-to-days starting))
(now (time-to-days current))
(end (time-to-days ending))
- (graph (make-string (1+ (- end start)) ?\ ))
+ (graph (make-string (1+ (- end start)) ?\s))
(index 0)
last-done-date)
(while (and done-dates (< (car done-dates) start))
@@ -304,18 +323,55 @@ current time."
(while (< start end)
(let* ((in-the-past-p (< start now))
(todayp (= start now))
- (donep (and done-dates
- (= start (car done-dates))))
- (faces (if (and in-the-past-p
- (not last-done-date)
- (not (< scheduled now)))
- '(org-habit-clear-face . org-habit-clear-future-face)
- (org-habit-get-faces
- habit start (and in-the-past-p
- (if last-done-date
- (+ last-done-date s-repeat)
- scheduled))
- donep)))
+ (donep (and done-dates (= start (car done-dates))))
+ (faces
+ (if (and in-the-past-p
+ (not last-done-date)
+ (not (< scheduled now)))
+ '(org-habit-clear-face . org-habit-clear-future-face)
+ (org-habit-get-faces
+ habit start
+ (and in-the-past-p
+ last-done-date
+ ;; Compute scheduled time for habit at the time
+ ;; START was current.
+ (let ((type (org-habit-repeat-type habit)))
+ (cond
+ ;; At the last done date, use current
+ ;; scheduling in all cases.
+ ((null done-dates) scheduled)
+ ((equal type ".+") (+ last-done-date s-repeat))
+ ((equal type "+")
+ ;; Since LAST-DONE-DATE, each done mark
+ ;; shifted scheduled date by S-REPEAT.
+ (- scheduled (* (length done-dates) s-repeat)))
+ (t
+ ;; Compute the scheduled time after the
+ ;; first repeat. This is the closest time
+ ;; past FIRST-DONE which can reach SCHEDULED
+ ;; by a number of S-REPEAT hops.
+ ;;
+ ;; Then, play TODO state change history from
+ ;; the beginning in order to find current
+ ;; scheduled time.
+ (let* ((first-done (car all-done-dates))
+ (s (let ((shift (mod (- scheduled first-done)
+ s-repeat)))
+ (+ (if (= shift 0) s-repeat shift)
+ first-done))))
+ (if (= first-done last-done-date) s
+ (catch :exit
+ (dolist (done (cdr all-done-dates) s)
+ ;; Each repeat shifts S by any
+ ;; number of S-REPEAT hops it takes
+ ;; to get past DONE, with a minimum
+ ;; of one hop.
+ (cl-incf s (* (1+ (/ (max (- done s) 0)
+ s-repeat))
+ s-repeat))
+ (when (= done last-done-date)
+ (throw :exit s))))))))))
+ donep)))
markedp face)
(if donep
(let ((done-time (time-add
@@ -348,7 +404,7 @@ current time."
(defun org-habit-insert-consistency-graphs (&optional line)
"Insert consistency graph for any habitual tasks."
- (let ((inhibit-read-only t) l c
+ (let ((inhibit-read-only t)
(buffer-invisibility-spec '(org-link))
(moment (time-subtract (current-time)
(list 0 (* 3600 org-extend-today-until) 0))))
diff --git a/lisp/org/org-id.el b/lisp/org/org-id.el
index 54fc733578d..f07d243b8cf 100644
--- a/lisp/org/org-id.el
+++ b/lisp/org/org-id.el
@@ -1,4 +1,4 @@
-;;; org-id.el --- Global identifiers for Org-mode entries
+;;; org-id.el --- Global identifiers for Org entries -*- lexical-binding: t; -*-
;;
;; Copyright (C) 2008-2017 Free Software Foundation, Inc.
;;
@@ -24,7 +24,7 @@
;;
;;; Commentary:
-;; This file implements globally unique identifiers for Org-mode entries.
+;; This file implements globally unique identifiers for Org entries.
;; Identifiers are stored in the entry as an :ID: property. Functions
;; are provided that create and retrieve such identifiers, and that find
;; entries based on the identifier.
@@ -73,20 +73,17 @@
(require 'org)
(declare-function message-make-fqdn "message" ())
-(declare-function org-pop-to-buffer-same-window
- "org-compat" (&optional buffer-or-name norecord label))
;;; Customization
(defgroup org-id nil
- "Options concerning global entry identifiers in Org-mode."
+ "Options concerning global entry identifiers in Org mode."
:tag "Org ID"
:group 'org)
-(define-obsolete-variable-alias
- 'org-link-to-org-use-id 'org-id-link-to-org-use-id "24.3")
(defcustom org-id-link-to-org-use-id nil
"Non-nil means storing a link to an Org file will use entry IDs.
+\\\
The variable can have the following values:
@@ -101,7 +98,7 @@ create-if-interactive
call `org-capture' that automatically and preemptively creates a
link. If you do want to get an ID link in a capture template to
an entry not having an ID, create it first by explicitly creating
- a link to it, using `C-c C-l' first.
+ a link to it, using `\\[org-store-link]' first.
create-if-interactive-and-no-custom-id
Like create-if-interactive, but do not create an ID if there is
@@ -203,7 +200,7 @@ This variable is only relevant when `org-id-track-globally' is set."
When Org reparses files to remake the list of files and IDs it is tracking,
it will normally scan the agenda files, the archives related to agenda files,
any files that are listed as ID containing in the current register, and
-any Org-mode files currently visited by Emacs.
+any Org file currently visited by Emacs.
You can list additional files here.
This variable is only relevant when `org-id-track-globally' is set."
:group 'org-id
@@ -277,7 +274,7 @@ If necessary, the ID is created."
(move-marker pom nil))))
;;;###autoload
-(defun org-id-get-with-outline-drilling (&optional targets)
+(defun org-id-get-with-outline-drilling ()
"Use an outline-cycling interface to retrieve the ID of an entry.
This only finds entries in the current buffer, using `org-get-location'.
It returns the ID of the entry. If necessary, the ID is created."
@@ -294,7 +291,7 @@ Move the cursor to that entry in that buffer."
(let ((m (org-id-find id 'marker)))
(unless m
(error "Cannot find entry with ID \"%s\"" id))
- (org-pop-to-buffer-same-window (marker-buffer m))
+ (pop-to-buffer-same-window (marker-buffer m))
(goto-char m)
(move-marker m nil)
(org-show-context)))
@@ -447,8 +444,7 @@ and time is the usual three-integer representation of time."
Store the relation between files and corresponding IDs.
This will scan all agenda files, all associated archives, and all
files currently mentioned in `org-id-locations'.
-When FILES is given, scan these files instead.
-When CHECK is given, prepare detailed information about duplicate IDs."
+When FILES is given, scan these files instead."
(interactive)
(if (not org-id-track-globally)
(error "Please turn on `org-id-track-globally' if you want to track IDs")
@@ -466,7 +462,7 @@ When CHECK is given, prepare detailed information about duplicate IDs."
(if (symbolp org-id-extra-files)
(symbol-value org-id-extra-files)
org-id-extra-files)
- ;; Files associated with live org-mode buffers
+ ;; Files associated with live Org buffers
(delq nil
(mapcar (lambda (b)
(with-current-buffer b
@@ -494,7 +490,7 @@ When CHECK is given, prepare detailed information about duplicate IDs."
(goto-char (point-min))
(while (re-search-forward "^[ \t]*:ID:[ \t]+\\(\\S-+\\)[ \t]*$"
nil t)
- (setq id (org-match-string-no-properties 1))
+ (setq id (match-string-no-properties 1))
(if (member id found)
(progn
(message "Duplicate ID \"%s\", also in file %s"
@@ -678,7 +674,7 @@ optional argument MARKERP, return the position as a new marker."
(move-marker m nil)
(org-show-context)))
-(org-add-link-type "id" 'org-id-open)
+(org-link-set-parameters "id" :follow #'org-id-open)
(provide 'org-id)
diff --git a/lisp/org/org-indent.el b/lisp/org/org-indent.el
index baaff2ff7c8..10c96179b61 100644
--- a/lisp/org/org-indent.el
+++ b/lisp/org/org-indent.el
@@ -1,4 +1,5 @@
-;;; org-indent.el --- Dynamic indentation for Org-mode
+;;; org-indent.el --- Dynamic indentation for Org -*- lexical-binding: t; -*-
+
;; Copyright (C) 2009-2017 Free Software Foundation, Inc.
;;
;; Author: Carsten Dominik
@@ -39,8 +40,7 @@
(require 'org-compat)
(require 'org)
-(eval-when-compile
- (require 'cl))
+(require 'cl-lib)
(declare-function org-inlinetask-get-task-level "org-inlinetask" ())
(declare-function org-inlinetask-in-task-p "org-inlinetask" ())
@@ -52,20 +52,6 @@
:tag "Org Indent"
:group 'org)
-(defconst org-indent-max 40
- "Maximum indentation in characters.")
-(defconst org-indent-max-levels 20
- "Maximum added level through virtual indentation, in characters.
-
-It is computed by multiplying `org-indent-indentation-per-level'
-minus one by actual level of the headline minus one.")
-
-(defvar org-indent-strings nil
- "Vector with all indentation strings.
-It will be set in `org-indent-initialize'.")
-(defvar org-indent-stars nil
- "Vector with all indentation star strings.
-It will be set in `org-indent-initialize'.")
(defvar org-indent-inlinetask-first-star (org-add-props "*" '(face org-warning))
"First star of inline tasks, with correct face.")
(defvar org-indent-agent-timer nil
@@ -82,7 +68,7 @@ Delay used when the buffer to initialize is current.")
Delay used when the buffer to initialize isn't current.")
(defvar org-indent-agent-resume-delay '(0 0 100000)
"Minimal time for other idle processes before switching back to agent.")
-(defvar org-indent-initial-marker nil
+(defvar org-indent--initial-marker nil
"Position of initialization before interrupt.
This is used locally in each buffer being initialized.")
(defvar org-hide-leading-stars-before-indent-mode nil
@@ -92,15 +78,12 @@ This is used locally in each buffer being initialized.")
It is modified by `org-indent-notify-modified-headline'.")
-(defcustom org-indent-boundary-char ?\ ; comment to protect space char
+(defcustom org-indent-boundary-char ?\s
"The end of the virtual indentation strings, a single-character string.
The default is just a space, but if you wish, you can use \"|\" or so.
This can be useful on a terminal window - under a windowing system,
-it may be prettier to customize the org-indent face."
+it may be prettier to customize the `org-indent' face."
:group 'org-indent
- :set (lambda (var val)
- (set var val)
- (and org-indent-strings (org-indent-initialize)))
:type 'character)
(defcustom org-indent-mode-turns-off-org-adapt-indentation t
@@ -121,29 +104,56 @@ turn on `org-hide-leading-stars'."
:group 'org-indent
:type 'integer)
-(defface org-indent
- (org-compatible-face nil nil)
+(defface org-indent '((t (:inherit org-hide)))
"Face for outline indentation.
The default is to make it look like whitespace. But you may find it
useful to make it ever so slightly different."
:group 'org-faces)
-(defun org-indent-initialize ()
- "Initialize the indentation strings."
- (setq org-indent-strings (make-vector (1+ org-indent-max) nil))
- (setq org-indent-stars (make-vector (1+ org-indent-max) nil))
- (aset org-indent-strings 0 nil)
- (aset org-indent-stars 0 nil)
- (loop for i from 1 to org-indent-max do
- (aset org-indent-strings i
- (org-add-props
- (concat (make-string (1- i) ?\ )
- (char-to-string org-indent-boundary-char))
+(defvar org-indent--text-line-prefixes nil
+ "Vector containing line prefixes strings for regular text.")
+
+(defvar org-indent--heading-line-prefixes nil
+ "Vector containing line prefix strings for headlines.")
+
+(defvar org-indent--inlinetask-line-prefixes nil
+ "Vector containing line prefix strings for inline tasks.")
+
+(defconst org-indent--deepest-level 50
+ "Maximum theoretical headline depth.")
+
+(defun org-indent--compute-prefixes ()
+ "Compute prefix strings for regular text and headlines."
+ (setq org-indent--heading-line-prefixes
+ (make-vector org-indent--deepest-level nil))
+ (setq org-indent--inlinetask-line-prefixes
+ (make-vector org-indent--deepest-level nil))
+ (setq org-indent--text-line-prefixes
+ (make-vector org-indent--deepest-level nil))
+ (dotimes (n org-indent--deepest-level)
+ (let ((indentation (if (<= n 1) 0
+ (* (1- org-indent-indentation-per-level)
+ (1- n)))))
+ ;; Headlines line prefixes.
+ (let ((heading-prefix (make-string indentation ?*)))
+ (aset org-indent--heading-line-prefixes
+ n
+ (org-add-props heading-prefix nil 'face 'org-indent))
+ ;; Inline tasks line prefixes
+ (aset org-indent--inlinetask-line-prefixes
+ n
+ (org-add-props (if (bound-and-true-p org-inlinetask-show-first-star)
+ (concat org-indent-inlinetask-first-star
+ (substring heading-prefix 1))
+ heading-prefix)
nil 'face 'org-indent)))
- (loop for i from 1 to org-indent-max-levels do
- (aset org-indent-stars i
- (org-add-props (make-string i ?*)
- nil 'face 'org-hide))))
+ ;; Text line prefixes.
+ (aset org-indent--text-line-prefixes
+ n
+ (concat (org-add-props (make-string (+ n indentation) ?\s)
+ nil 'face 'org-indent)
+ (and (> n 0)
+ (char-to-string org-indent-boundary-char)))))))
(defsubst org-indent-remove-properties (beg end)
"Remove indentations between BEG and END."
@@ -162,34 +172,25 @@ buffer, which can take a few seconds on large buffers, is done
during idle time."
nil " Ind" nil
(cond
- ((and org-indent-mode (featurep 'xemacs))
- (message "org-indent-mode does not work in XEmacs - refusing to turn it on")
- (setq org-indent-mode nil))
- ((and org-indent-mode
- (not (org-version-check "23.1.50" "Org Indent mode" :predicate)))
- (message "org-indent-mode can crash Emacs 23.1 - refusing to turn it on!")
- (ding)
- (sit-for 1)
- (setq org-indent-mode nil))
(org-indent-mode
;; mode was turned on.
- (org-set-local 'indent-tabs-mode nil)
- (or org-indent-strings (org-indent-initialize))
- (org-set-local 'org-indent-initial-marker (copy-marker 1))
+ (setq-local indent-tabs-mode nil)
+ (setq-local org-indent--initial-marker (copy-marker 1))
(when org-indent-mode-turns-off-org-adapt-indentation
- (org-set-local 'org-adapt-indentation nil))
+ (setq-local org-adapt-indentation nil))
(when org-indent-mode-turns-on-hiding-stars
- (org-set-local 'org-hide-leading-stars-before-indent-mode
- org-hide-leading-stars)
- (org-set-local 'org-hide-leading-stars t))
- (org-add-hook 'filter-buffer-substring-functions
- (lambda (fun start end delete)
- (org-indent-remove-properties-from-string
- (funcall fun start end delete)))
- nil t)
- (org-add-hook 'after-change-functions 'org-indent-refresh-maybe nil 'local)
- (org-add-hook 'before-change-functions
- 'org-indent-notify-modified-headline nil 'local)
+ (setq-local org-hide-leading-stars-before-indent-mode
+ org-hide-leading-stars)
+ (setq-local org-hide-leading-stars t))
+ (org-indent--compute-prefixes)
+ (add-hook 'filter-buffer-substring-functions
+ (lambda (fun start end delete)
+ (org-indent-remove-properties-from-string
+ (funcall fun start end delete)))
+ nil t)
+ (add-hook 'after-change-functions 'org-indent-refresh-maybe nil 'local)
+ (add-hook 'before-change-functions
+ 'org-indent-notify-modified-headline nil 'local)
(and font-lock-mode (org-restart-font-lock))
(org-indent-remove-properties (point-min) (point-max))
;; Submit current buffer to initialize agent. If it's the first
@@ -205,11 +206,11 @@ during idle time."
(kill-local-variable 'org-adapt-indentation)
(setq org-indent-agentized-buffers
(delq (current-buffer) org-indent-agentized-buffers))
- (when (markerp org-indent-initial-marker)
- (set-marker org-indent-initial-marker nil))
+ (when (markerp org-indent--initial-marker)
+ (set-marker org-indent--initial-marker nil))
(when (boundp 'org-hide-leading-stars-before-indent-mode)
- (org-set-local 'org-hide-leading-stars
- org-hide-leading-stars-before-indent-mode))
+ (setq-local org-hide-leading-stars
+ org-hide-leading-stars-before-indent-mode))
(remove-hook 'filter-buffer-substring-functions
(lambda (fun start end delete)
(org-indent-remove-properties-from-string
@@ -245,7 +246,7 @@ When no more buffer is being watched, the agent suppress itself."
(when org-indent-agent-resume-timer
(cancel-timer org-indent-agent-resume-timer))
(setq org-indent-agentized-buffers
- (org-remove-if-not #'buffer-live-p org-indent-agentized-buffers))
+ (cl-remove-if-not #'buffer-live-p org-indent-agentized-buffers))
(cond
;; Job done: kill agent.
((not org-indent-agentized-buffers) (cancel-timer org-indent-agent-timer))
@@ -269,46 +270,44 @@ a time value."
(let ((interruptp
;; Always nil unless interrupted.
(catch 'interrupt
- (and org-indent-initial-marker
- (marker-position org-indent-initial-marker)
- (org-indent-add-properties org-indent-initial-marker
+ (and org-indent--initial-marker
+ (marker-position org-indent--initial-marker)
+ (equal (marker-buffer org-indent--initial-marker)
+ buffer)
+ (org-indent-add-properties org-indent--initial-marker
(point-max)
delay)
nil))))
- (move-marker org-indent-initial-marker interruptp)
+ (move-marker org-indent--initial-marker interruptp)
;; Job is complete: un-agentize buffer.
(unless interruptp
(setq org-indent-agentized-buffers
(delq buffer org-indent-agentized-buffers))))))))
-(defsubst org-indent-set-line-properties (l w h)
+(defun org-indent-set-line-properties (level indentation &optional heading)
"Set prefix properties on current line an move to next one.
-Prefix properties `line-prefix' and `wrap-prefix' in current line
-are set to, respectively, length L and W.
-
-If H is non-nil, `line-prefix' will be starred. If H is
-`inline', the first star will have `org-warning' face.
-
-Assume point is at beginning of line."
- (let ((line (cond
- ((eq 'inline h)
- (let ((stars (aref org-indent-stars
- (min l org-indent-max-levels))))
- (and stars
- (if (org-bound-and-true-p org-inlinetask-show-first-star)
- (concat org-indent-inlinetask-first-star
- (substring stars 1))
- stars))))
- (h (aref org-indent-stars
- (min l org-indent-max-levels)))
- (t (aref org-indent-strings
- (min l org-indent-max)))))
- (wrap (aref org-indent-strings (min w org-indent-max))))
+LEVEL is the current level of heading. INDENTATION is the
+expected indentation when wrapping line.
+
+When optional argument HEADING is non-nil, assume line is at
+a heading. Moreover, if is is `inlinetask', the first star will
+have `org-warning' face."
+ (let* ((line (aref (pcase heading
+ (`nil org-indent--text-line-prefixes)
+ (`inlinetask org-indent--inlinetask-line-prefixes)
+ (_ org-indent--heading-line-prefixes))
+ level))
+ (wrap
+ (org-add-props
+ (concat line
+ (if heading (concat (make-string level ?*) " ")
+ (make-string indentation ?\s)))
+ nil 'face 'org-indent)))
;; Add properties down to the next line to indent empty lines.
- (add-text-properties (point) (min (1+ (point-at-eol)) (point-max))
+ (add-text-properties (line-beginning-position) (line-beginning-position 2)
`(line-prefix ,line wrap-prefix ,wrap)))
- (forward-line 1))
+ (forward-line))
(defun org-indent-add-properties (beg end &optional delay)
"Add indentation properties between BEG and END.
@@ -322,26 +321,14 @@ stopped."
(org-with-wide-buffer
(goto-char beg)
(beginning-of-line)
- ;; 1. Initialize prefix at BEG. This is done by storing two
- ;; variables: INLINE-PF and PF, representing respectively
- ;; length of current `line-prefix' when line is inside an
- ;; inline task or not.
+ ;; Initialize prefix at BEG, according to current entry's level.
(let* ((case-fold-search t)
(limited-re (org-get-limited-outline-regexp))
- (added-ind-per-lvl (abs (1- org-indent-indentation-per-level)))
- (pf (save-excursion
- (and (ignore-errors (let ((outline-regexp limited-re))
- (org-back-to-heading t)))
- (+ (* org-indent-indentation-per-level
- (- (match-end 0) (match-beginning 0) 2)) 2))))
- (pf-inline (and (featurep 'org-inlinetask)
- (org-inlinetask-in-task-p)
- (+ (* org-indent-indentation-per-level
- (1- (org-inlinetask-get-task-level))) 2)))
+ (level (or (org-current-level) 0))
(time-limit (and delay (time-add (current-time) delay))))
- ;; 2. For each line, set `line-prefix' and `wrap-prefix'
- ;; properties depending on the type of line (headline,
- ;; inline task, item or other).
+ ;; For each line, set `line-prefix' and `wrap-prefix'
+ ;; properties depending on the type of line (headline, inline
+ ;; task, item or other).
(org-with-silent-modifications
(while (and (<= (point) end) (not (eobp)))
(cond
@@ -354,38 +341,23 @@ stopped."
((and delay (time-less-p time-limit (current-time)))
(setq org-indent-agent-resume-timer
(run-with-idle-timer
- (time-add (current-idle-time)
- org-indent-agent-resume-delay)
+ (time-add (current-idle-time) org-indent-agent-resume-delay)
nil #'org-indent-initialize-agent))
(throw 'interrupt (point)))
;; Headline or inline task.
((looking-at org-outline-regexp)
(let* ((nstars (- (match-end 0) (match-beginning 0) 1))
- (line (* added-ind-per-lvl (1- nstars)))
- (wrap (+ line (1+ nstars))))
- (cond
- ;; Headline: new value for PF.
- ((looking-at limited-re)
- (org-indent-set-line-properties line wrap t)
- (setq pf wrap))
- ;; End of inline task: PF-INLINE is now nil.
- ((looking-at "\\*+ end[ \t]*$")
- (org-indent-set-line-properties line wrap 'inline)
- (setq pf-inline nil))
- ;; Start of inline task. Determine if it contains
- ;; text, or if it is only one line long. Set
- ;; PF-INLINE accordingly.
- (t (org-indent-set-line-properties line wrap 'inline)
- (setq pf-inline (and (org-inlinetask-in-task-p) wrap))))))
+ (type (or (looking-at-p limited-re) 'inlinetask)))
+ (org-indent-set-line-properties nstars 0 type)
+ ;; At an headline, define new value for LEVEL.
+ (unless (eq type 'inlinetask) (setq level nstars))))
;; List item: `wrap-prefix' is set where body starts.
((org-at-item-p)
- (let* ((line (or pf-inline pf 0))
- (wrap (+ (org-list-item-body-column (point)) line)))
- (org-indent-set-line-properties line wrap nil)))
- ;; Normal line: use PF-INLINE, PF or nil as prefixes.
- (t (let* ((line (or pf-inline pf 0))
- (wrap (+ line (org-get-indentation))))
- (org-indent-set-line-properties line wrap nil))))))))))
+ (org-indent-set-line-properties
+ level (org-list-item-body-column (point))))
+ ;; Regular line.
+ (t
+ (org-indent-set-line-properties level (org-get-indentation))))))))))
(defun org-indent-notify-modified-headline (beg end)
"Set `org-indent-modified-headline-flag' depending on context.
@@ -398,13 +370,14 @@ Flag will be non-nil if command is going to modify or delete an
headline."
(when org-indent-mode
(setq org-indent-modified-headline-flag
- (save-excursion
- (goto-char beg)
- (save-match-data
- (or (and (org-at-heading-p) (< beg (match-end 0)))
- (re-search-forward org-outline-regexp-bol end t)))))))
-
-(defun org-indent-refresh-maybe (beg end dummy)
+ (org-with-wide-buffer
+ (goto-char beg)
+ (save-match-data
+ (or (and (org-at-heading-p) (< beg (match-end 0)))
+ (re-search-forward
+ (org-with-limited-levels org-outline-regexp-bol) end t)))))))
+
+(defun org-indent-refresh-maybe (beg end _)
"Refresh indentation properties in an adequate portion of buffer.
BEG and END are the positions of the beginning and end of the
range of inserted text. DUMMY is an unused argument.
@@ -414,19 +387,21 @@ This function is meant to be called by `after-change-functions'."
(save-match-data
;; If a headline was modified or inserted, set properties until
;; next headline.
- (if (or org-indent-modified-headline-flag
- (save-excursion
- (goto-char beg)
- (beginning-of-line)
- (re-search-forward org-outline-regexp-bol end t)))
- (let ((end (save-excursion
- (goto-char end)
- (org-with-limited-levels (outline-next-heading))
- (point))))
- (setq org-indent-modified-headline-flag nil)
- (org-indent-add-properties beg end))
- ;; Otherwise, only set properties on modified area.
- (org-indent-add-properties beg end)))))
+ (org-with-wide-buffer
+ (if (or org-indent-modified-headline-flag
+ (save-excursion
+ (goto-char beg)
+ (beginning-of-line)
+ (re-search-forward
+ (org-with-limited-levels org-outline-regexp-bol) end t)))
+ (let ((end (save-excursion
+ (goto-char end)
+ (org-with-limited-levels (outline-next-heading))
+ (point))))
+ (setq org-indent-modified-headline-flag nil)
+ (org-indent-add-properties beg end))
+ ;; Otherwise, only set properties on modified area.
+ (org-indent-add-properties beg end))))))
(provide 'org-indent)
diff --git a/lisp/org/org-info.el b/lisp/org/org-info.el
index c8f6f06de06..79b9bcc3d96 100644
--- a/lisp/org/org-info.el
+++ b/lisp/org/org-info.el
@@ -1,4 +1,4 @@
-;;; org-info.el --- Support for links to Info nodes from within Org-Mode
+;;; org-info.el --- Support for Links to Info Nodes -*- lexical-binding: t; -*-
;; Copyright (C) 2004-2017 Free Software Foundation, Inc.
@@ -24,8 +24,8 @@
;;
;;; Commentary:
-;; This file implements links to Info nodes from within Org-mode.
-;; Org-mode loads this module by default - if this is not what you want,
+;; This file implements links to Info nodes from within Org mode.
+;; Org mode loads this module by default - if this is not what you want,
;; configure the variable `org-modules'.
;;; Code:
@@ -40,19 +40,20 @@
(defvar Info-current-node)
;; Install the link type
-(org-add-link-type "info" 'org-info-open)
-(add-hook 'org-store-link-functions 'org-info-store-link)
+(org-link-set-parameters "info"
+ :follow #'org-info-open
+ :export #'org-info-export
+ :store #'org-info-store-link)
;; Implementation
(defun org-info-store-link ()
"Store a link to an Info file and node."
(when (eq major-mode 'Info-mode)
- (let (link desc)
- (setq link (concat "info:"
- (file-name-nondirectory Info-current-file)
- "#" Info-current-node))
- (setq desc (concat (file-name-nondirectory Info-current-file)
- "#" Info-current-node))
+ (let ((link (concat "info:"
+ (file-name-nondirectory Info-current-file)
+ "#" Info-current-node))
+ (desc (concat (file-name-nondirectory Info-current-file)
+ "#" Info-current-node)))
(org-store-link-props :type "info" :file Info-current-file
:node Info-current-node
:link link :desc desc)
@@ -67,12 +68,76 @@
"Follow an Info file and node link specified by NAME."
(if (or (string-match "\\(.*\\)[#:]:?\\(.*\\)" name)
(string-match "\\(.*\\)" name))
- (progn
+ (let ((filename (match-string 1 name))
+ (nodename-or-index (or (match-string 2 name) "Top")))
(require 'info)
- (if (match-string 2 name) ; If there isn't a node, choose "Top"
- (Info-find-node (match-string 1 name) (match-string 2 name))
- (Info-find-node (match-string 1 name) "Top")))
- (message "Could not open: %s" name)))
+ ;; If nodename-or-index is invalid node name, then look it up
+ ;; in the index.
+ (condition-case nil
+ (Info-find-node filename nodename-or-index)
+ (user-error (Info-find-node filename "Top")
+ (condition-case nil
+ (Info-index nodename-or-index)
+ (user-error "Could not find '%s' node or index entry"
+ nodename-or-index)))))
+ (user-error "Could not open: %s" name)))
+
+(defconst org-info-emacs-documents
+ '("ada-mode" "auth" "autotype" "bovine" "calc" "ccmode" "cl" "dbus" "dired-x"
+ "ebrowse" "ede" "ediff" "edt" "efaq-w32" "efaq" "eieio" "eintr" "elisp"
+ "emacs-gnutls" "emacs-mime" "emacs" "epa" "erc" "ert" "eshell" "eudc" "eww"
+ "flymake" "forms" "gnus" "htmlfontify" "idlwave" "ido" "info" "mairix-el"
+ "message" "mh-e" "newsticker" "nxml-mode" "octave-mode" "org" "pcl-cvs"
+ "pgg" "rcirc" "reftex" "remember" "sasl" "sc" "semantic" "ses" "sieve"
+ "smtpmail" "speedbar" "srecode" "todo-mode" "tramp" "url" "vip" "viper"
+ "widget" "wisent" "woman")
+ "List of emacs documents available.
+Taken from ")
+
+(defconst org-info-other-documents
+ '(("libc" . "http://www.gnu.org/software/libc/manual/html_mono/libc.html")
+ ("make" . "http://www.gnu.org/software/make/manual/make.html"))
+ "Alist of documents generated from Texinfo source.
+When converting info links to HTML, links to any one of these manuals are
+converted to use these URL.")
+
+(defun org-info-map-html-url (filename)
+ "Return URL or HTML file associated to Info FILENAME.
+If FILENAME refers to an official GNU document, return a URL pointing to
+the official page for that document, e.g., use \"gnu.org\" for all Emacs
+related documents. Otherwise, append \".html\" extension to FILENAME.
+See `org-info-emacs-documents' and `org-info-other-documents' for details."
+ (cond ((member filename org-info-emacs-documents)
+ (format "http://www.gnu.org/software/emacs/manual/html_mono/%s.html"
+ filename))
+ ((cdr (assoc filename org-info-other-documents)))
+ (t (concat filename ".html"))))
+
+(defun org-info--expand-node-name (node)
+ "Expand Info NODE to HTML cross reference."
+ ;; See (info "(texinfo) HTML Xref Node Name Expansion") for the
+ ;; expansion rule.
+ (let ((node (replace-regexp-in-string
+ "\\([ \t\n\r]+\\)\\|\\([^a-zA-Z0-9]\\)"
+ (lambda (m)
+ (if (match-end 1) "-" (format "_%04x" (string-to-char m))))
+ (org-trim node))))
+ (cond ((string= node "") "")
+ ((string-match-p "\\`[0-9]" node) (concat "g_t" node))
+ (t node))))
+
+(defun org-info-export (path desc format)
+ "Export an info link.
+See `org-link-parameters' for details about PATH, DESC and FORMAT."
+ (when (eq format 'html)
+ (or (string-match "\\(.*\\)[#:]:?\\(.*\\)" path)
+ (string-match "\\(.*\\)" path))
+ (let ((filename (match-string 1 path))
+ (node (or (match-string 2 path) "Top")))
+ (format "%s"
+ (org-info-map-html-url filename)
+ (org-info--expand-node-name node)
+ (or desc path)))))
(provide 'org-info)
diff --git a/lisp/org/org-inlinetask.el b/lisp/org/org-inlinetask.el
index bf4ab205a4c..2918d4061dd 100644
--- a/lisp/org/org-inlinetask.el
+++ b/lisp/org/org-inlinetask.el
@@ -1,4 +1,4 @@
-;;; org-inlinetask.el --- Tasks independent of outline hierarchy
+;;; org-inlinetask.el --- Tasks Independent of Outline Hierarchy -*- lexical-binding: t; -*-
;; Copyright (C) 2009-2017 Free Software Foundation, Inc.
;;
@@ -26,7 +26,7 @@
;;
;;; Commentary:
;;
-;; This module implements inline tasks in Org-mode. Inline tasks are
+;; This module implements inline tasks in Org mode. Inline tasks are
;; tasks that have all the properties of normal outline nodes,
;; including the ability to store meta data like scheduling dates,
;; TODO state, tags and properties. However, these nodes are treated
@@ -108,7 +108,6 @@ When nil, the first star is not shown."
(defvar org-odd-levels-only)
(defvar org-keyword-time-regexp)
-(defvar org-drawer-regexp)
(defvar org-complex-heading-regexp)
(defvar org-property-end-re)
@@ -168,9 +167,9 @@ The number of levels is controlled by `org-inlinetask-min-level'."
(stars-re (org-inlinetask-outline-regexp))
(task-beg-re (concat stars-re "\\(?:.*\\)"))
(task-end-re (concat stars-re "END[ \t]*$")))
- (or (org-looking-at-p task-beg-re)
+ (or (looking-at-p task-beg-re)
(and (re-search-forward "^\\*+[ \t]+" nil t)
- (progn (beginning-of-line) (org-looking-at-p task-end-re)))))))
+ (progn (beginning-of-line) (looking-at-p task-end-re)))))))
(defun org-inlinetask-goto-beginning ()
"Go to the beginning of the inline task at point."
@@ -178,7 +177,7 @@ The number of levels is controlled by `org-inlinetask-min-level'."
(let ((case-fold-search t)
(inlinetask-re (org-inlinetask-outline-regexp)))
(re-search-backward inlinetask-re nil t)
- (when (org-looking-at-p (concat inlinetask-re "END[ \t]*$"))
+ (when (looking-at-p (concat inlinetask-re "END[ \t]*$"))
(re-search-backward inlinetask-re nil t))))
(defun org-inlinetask-goto-end ()
@@ -190,17 +189,16 @@ Return point."
(inlinetask-re (org-inlinetask-outline-regexp))
(task-end-re (concat inlinetask-re "END[ \t]*$")))
(cond
- ((looking-at task-end-re) (forward-line))
+ ((looking-at task-end-re))
((looking-at inlinetask-re)
(forward-line)
(cond
- ((looking-at task-end-re) (forward-line))
+ ((looking-at task-end-re))
((looking-at inlinetask-re))
((org-inlinetask-in-task-p)
- (re-search-forward inlinetask-re nil t)
- (forward-line))))
- (t (re-search-forward inlinetask-re nil t)
- (forward-line)))
+ (re-search-forward inlinetask-re nil t))))
+ (t (re-search-forward inlinetask-re nil t)))
+ (end-of-line)
(point))))
(defun org-inlinetask-get-task-level ()
@@ -273,8 +271,7 @@ If the task has an end part, also demote it."
(defvar org-indent-indentation-per-level) ; defined in org-indent.el
-(defface org-inlinetask
- (org-compatible-face 'shadow '((t (:bold t))))
+(defface org-inlinetask '((t :inherit shadow))
"Face for inlinetask headlines."
:group 'org-faces)
@@ -288,7 +285,7 @@ If the task has an end part, also demote it."
",\\}\\)\\(\\*\\* .*\\)"))
;; Virtual indentation will add the warning face on the first
;; star. Thus, in that case, only hide it.
- (start-face (if (and (org-bound-and-true-p org-indent-mode)
+ (start-face (if (and (bound-and-true-p org-indent-mode)
(> org-indent-indentation-per-level 1))
'org-hide
'org-warning)))
@@ -315,19 +312,36 @@ If the task has an end part, also demote it."
;; Nothing to show/hide.
((= end start))
;; Inlinetask was folded: expand it.
- ((get-char-property (1+ start) 'invisible)
+ ((eq (get-char-property (1+ start) 'invisible) 'outline)
(outline-flag-region start end nil)
(org-cycle-hide-drawers 'children))
(t (outline-flag-region start end t)))))
+(defun org-inlinetask-hide-tasks (state)
+ "Hide inline tasks in buffer when STATE is `contents' or `children'.
+This function is meant to be used in `org-cycle-hook'."
+ (pcase state
+ (`contents
+ (let ((regexp (org-inlinetask-outline-regexp)))
+ (save-excursion
+ (goto-char (point-min))
+ (while (re-search-forward regexp nil t)
+ (org-inlinetask-toggle-visibility)
+ (org-inlinetask-goto-end)))))
+ (`children
+ (save-excursion
+ (while (and (outline-next-heading) (org-inlinetask-at-task-p))
+ (org-inlinetask-toggle-visibility)
+ (org-inlinetask-goto-end))))))
+
(defun org-inlinetask-remove-END-maybe ()
"Remove an END line when present."
(when (looking-at (format "\\([ \t]*\n\\)*\\*\\{%d,\\}[ \t]+END[ \t]*$"
org-inlinetask-min-level))
(replace-match "")))
-(eval-after-load "org"
- '(add-hook 'org-font-lock-hook 'org-inlinetask-fontify))
+(add-hook 'org-font-lock-hook 'org-inlinetask-fontify)
+(add-hook 'org-cycle-hook 'org-inlinetask-hide-tasks)
(provide 'org-inlinetask)
diff --git a/lisp/org/org-irc.el b/lisp/org/org-irc.el
index 1243587beb8..3a6a7f4db06 100644
--- a/lisp/org/org-irc.el
+++ b/lisp/org/org-irc.el
@@ -1,4 +1,4 @@
-;;; org-irc.el --- Store links to IRC sessions
+;;; org-irc.el --- Store Links to IRC Sessions -*- lexical-binding: t; -*-
;;
;; Copyright (C) 2008-2017 Free Software Foundation, Inc.
;;
@@ -22,8 +22,8 @@
;;; Commentary:
-;; This file implements links to an IRC session from within Org-mode.
-;; Org-mode loads this module by default - if this is not what you want,
+;; This file implements links to an IRC session from within Org mode.
+;; Org mode loads this module by default - if this is not what you want,
;; configure the variable `org-modules'.
;;
;; Please customize the variable `org-modules' to select
@@ -59,8 +59,6 @@
(declare-function erc-server-buffer "erc" ())
(declare-function erc-get-server-nickname-list "erc" ())
(declare-function erc-cmd-JOIN "erc" (channel &optional key))
-(declare-function org-pop-to-buffer-same-window
- "org-compat" (&optional buffer-or-name norecord label))
(defvar org-irc-client 'erc
"The IRC client to act on.")
@@ -73,9 +71,7 @@
;; Generic functions/config (extend these for other clients)
-(add-to-list 'org-store-link-functions 'org-irc-store-link)
-
-(org-add-link-type "irc" 'org-irc-visit nil)
+(org-link-set-parameters "irc" :follow #'org-irc-visit :store #'org-irc-store-link)
(defun org-irc-visit (link)
"Parse LINK and dispatch to the correct function based on the client found."
@@ -114,11 +110,9 @@ chars that the value AFTER with `...'"
(cons "[ \t]*$" "")
(cons (concat "^\\(.\\{" after
"\\}\\).*") "\\1..."))))
- (mapc (lambda (x)
- (when (string-match (car x) string)
- (setq string (replace-match (cdr x) nil nil string))))
- replace-map)
- string))
+ (dolist (x replace-map string)
+ (when (string-match (car x) string)
+ (setq string (replace-match (cdr x) nil nil string))))))
;; ERC specific functions
@@ -233,7 +227,7 @@ default."
(throw 'found x))))))
(if chan-buf
(progn
- (org-pop-to-buffer-same-window chan-buf)
+ (pop-to-buffer-same-window chan-buf)
;; if we got a nick, and they're in the chan,
;; then start a chat with them
(let ((nick (pop link)))
@@ -244,9 +238,9 @@ default."
(insert (concat nick ": ")))
(error "%s not found in %s" nick chan-name)))))
(progn
- (org-pop-to-buffer-same-window server-buffer)
+ (pop-to-buffer-same-window server-buffer)
(erc-cmd-JOIN chan-name))))
- (org-pop-to-buffer-same-window server-buffer)))
+ (pop-to-buffer-same-window server-buffer)))
;; no server match, make new connection
(erc-select :server server :port port))))
diff --git a/lisp/org/org-lint.el b/lisp/org/org-lint.el
new file mode 100644
index 00000000000..5abda7c4a6b
--- /dev/null
+++ b/lisp/org/org-lint.el
@@ -0,0 +1,1227 @@
+;;; org-lint.el --- Linting for Org documents -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2015-2017 Free Software Foundation, Inc.
+
+;; Author: Nicolas Goaziou
+;; Keywords: outlines, hypermedia, calendar, wp
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see .
+
+;;; Commentary:
+
+;; This library implements linting for Org syntax. The sole public
+;; function is `org-lint', which see.
+
+;; Internally, the library defines a new structure:
+;; `org-lint-checker', with the following slots:
+
+;; - NAME: Unique check identifier, as a non-nil symbol that doesn't
+;; start with an hyphen.
+;;
+;; The check is done calling the function `org-lint-NAME' with one
+;; mandatory argument, the parse tree describing the current Org
+;; buffer. Such function calls are wrapped within
+;; a `save-excursion' and point is always at `point-min'. Its
+;; return value has to be an alist (POSITION MESSAGE) when
+;; POSITION refer to the buffer position of the error, as an
+;; integer, and MESSAGE is a string describing the error.
+
+;; - DESCRIPTION: Summary about the check, as a string.
+
+;; - CATEGORIES: Categories relative to the check, as a list of
+;; symbol. They are used for filtering when calling `org-lint'.
+;; Checkers not explicitly associated to a category are collected
+;; in the `default' one.
+
+;; - TRUST: The trust level one can have in the check. It is either
+;; `low' or `high', depending on the heuristics implemented and
+;; the nature of the check. This has an indicative value only and
+;; is displayed along reports.
+
+;; All checks have to be listed in `org-lint--checkers'.
+
+;; Results are displayed in a special "*Org Lint*" buffer with
+;; a dedicated major mode, derived from `tabulated-list-mode'.
+;;
+;; In addition to the usual key-bindings inherited from it, "C-j" and
+;; "TAB" display problematic line reported under point whereas "RET"
+;; jumps to it. Also, "h" hides all reports similar to the current
+;; one. Additionally, "i" removes them from subsequent reports.
+
+;; Checks currently implemented are:
+
+;; - duplicate CUSTOM_ID properties
+;; - duplicate NAME values
+;; - duplicate targets
+;; - duplicate footnote definitions
+;; - orphaned affiliated keywords
+;; - obsolete affiliated keywords
+;; - missing language in src blocks
+;; - missing back-end in export blocks
+;; - invalid Babel call blocks
+;; - NAME values with a colon
+;; - deprecated export block syntax
+;; - deprecated Babel header properties
+;; - wrong header arguments in src blocks
+;; - misuse of CATEGORY keyword
+;; - "coderef" links with unknown destination
+;; - "custom-id" links with unknown destination
+;; - "fuzzy" links with unknown destination
+;; - "id" links with unknown destination
+;; - links to non-existent local files
+;; - SETUPFILE keywords with non-existent file parameter
+;; - INCLUDE keywords with wrong link parameter
+;; - obsolete markup in INCLUDE keyword
+;; - unknown items in OPTIONS keyword
+;; - spurious macro arguments or invalid macro templates
+;; - special properties in properties drawer
+;; - obsolete syntax for PROPERTIES drawers
+;; - missing definition for footnote references
+;; - missing reference for footnote definitions
+;; - non-footnote definitions in footnote section
+;; - probable invalid keywords
+;; - invalid blocks
+;; - misplaced planning info line
+;; - incomplete drawers
+;; - indented diary-sexps
+;; - obsolete QUOTE section
+;; - obsolete "file+application" link
+;; - blank headlines with tags
+
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'org-element)
+(require 'org-macro)
+(require 'ox)
+(require 'ob)
+
+
+;;; Checkers
+
+(cl-defstruct (org-lint-checker (:copier nil))
+ (name 'missing-checker-name)
+ (description "")
+ (categories '(default))
+ (trust 'high)) ; `low' or `high'
+
+(defun org-lint-missing-checker-name (_)
+ (error
+ "`A checker has no `:name' property. Please verify `org-lint--checkers'"))
+
+(defconst org-lint--checkers
+ (list
+ (make-org-lint-checker
+ :name 'duplicate-custom-id
+ :description "Report duplicates CUSTOM_ID properties"
+ :categories '(link))
+ (make-org-lint-checker
+ :name 'duplicate-name
+ :description "Report duplicate NAME values"
+ :categories '(babel link))
+ (make-org-lint-checker
+ :name 'duplicate-target
+ :description "Report duplicate targets"
+ :categories '(link))
+ (make-org-lint-checker
+ :name 'duplicate-footnote-definition
+ :description "Report duplicate footnote definitions"
+ :categories '(footnote))
+ (make-org-lint-checker
+ :name 'orphaned-affiliated-keywords
+ :description "Report orphaned affiliated keywords"
+ :trust 'low)
+ (make-org-lint-checker
+ :name 'obsolete-affiliated-keywords
+ :description "Report obsolete affiliated keywords"
+ :categories '(obsolete))
+ (make-org-lint-checker
+ :name 'deprecated-export-blocks
+ :description "Report deprecated export block syntax"
+ :categories '(obsolete export)
+ :trust 'low)
+ (make-org-lint-checker
+ :name 'deprecated-header-syntax
+ :description "Report deprecated Babel header syntax"
+ :categories '(obsolete babel)
+ :trust 'low)
+ (make-org-lint-checker
+ :name 'missing-language-in-src-block
+ :description "Report missing language in src blocks"
+ :categories '(babel))
+ (make-org-lint-checker
+ :name 'missing-backend-in-export-block
+ :description "Report missing back-end in export blocks"
+ :categories '(export))
+ (make-org-lint-checker
+ :name 'invalid-babel-call-block
+ :description "Report invalid Babel call blocks"
+ :categories '(babel))
+ (make-org-lint-checker
+ :name 'colon-in-name
+ :description "Report NAME values with a colon"
+ :categories '(babel))
+ (make-org-lint-checker
+ :name 'wrong-header-argument
+ :description "Report wrong babel headers"
+ :categories '(babel))
+ (make-org-lint-checker
+ :name 'wrong-header-value
+ :description "Report invalid value in babel headers"
+ :categories '(babel)
+ :trust 'low)
+ (make-org-lint-checker
+ :name 'deprecated-category-setup
+ :description "Report misuse of CATEGORY keyword"
+ :categories '(obsolete))
+ (make-org-lint-checker
+ :name 'invalid-coderef-link
+ :description "Report \"coderef\" links with unknown destination"
+ :categories '(link))
+ (make-org-lint-checker
+ :name 'invalid-custom-id-link
+ :description "Report \"custom-id\" links with unknown destination"
+ :categories '(link))
+ (make-org-lint-checker
+ :name 'invalid-fuzzy-link
+ :description "Report \"fuzzy\" links with unknown destination"
+ :categories '(link))
+ (make-org-lint-checker
+ :name 'invalid-id-link
+ :description "Report \"id\" links with unknown destination"
+ :categories '(link))
+ (make-org-lint-checker
+ :name 'link-to-local-file
+ :description "Report links to non-existent local files"
+ :categories '(link)
+ :trust 'low)
+ (make-org-lint-checker
+ :name 'non-existent-setupfile-parameter
+ :description "Report SETUPFILE keywords with non-existent file parameter"
+ :trust 'low)
+ (make-org-lint-checker
+ :name 'wrong-include-link-parameter
+ :description "Report INCLUDE keywords with misleading link parameter"
+ :categories '(export)
+ :trust 'low)
+ (make-org-lint-checker
+ :name 'obsolete-include-markup
+ :description "Report obsolete markup in INCLUDE keyword"
+ :categories '(obsolete export)
+ :trust 'low)
+ (make-org-lint-checker
+ :name 'unknown-options-item
+ :description "Report unknown items in OPTIONS keyword"
+ :categories '(export)
+ :trust 'low)
+ (make-org-lint-checker
+ :name 'invalid-macro-argument-and-template
+ :description "Report spurious macro arguments or invalid macro templates"
+ :categories '(export)
+ :trust 'low)
+ (make-org-lint-checker
+ :name 'special-property-in-properties-drawer
+ :description "Report special properties in properties drawers"
+ :categories '(properties))
+ (make-org-lint-checker
+ :name 'obsolete-properties-drawer
+ :description "Report obsolete syntax for properties drawers"
+ :categories '(obsolete properties))
+ (make-org-lint-checker
+ :name 'undefined-footnote-reference
+ :description "Report missing definition for footnote references"
+ :categories '(footnote))
+ (make-org-lint-checker
+ :name 'unreferenced-footnote-definition
+ :description "Report missing reference for footnote definitions"
+ :categories '(footnote))
+ (make-org-lint-checker
+ :name 'extraneous-element-in-footnote-section
+ :description "Report non-footnote definitions in footnote section"
+ :categories '(footnote))
+ (make-org-lint-checker
+ :name 'invalid-keyword-syntax
+ :description "Report probable invalid keywords"
+ :trust 'low)
+ (make-org-lint-checker
+ :name 'invalid-block
+ :description "Report invalid blocks"
+ :trust 'low)
+ (make-org-lint-checker
+ :name 'misplaced-planning-info
+ :description "Report misplaced planning info line"
+ :trust 'low)
+ (make-org-lint-checker
+ :name 'incomplete-drawer
+ :description "Report probable incomplete drawers"
+ :trust 'low)
+ (make-org-lint-checker
+ :name 'indented-diary-sexp
+ :description "Report probable indented diary-sexps"
+ :trust 'low)
+ (make-org-lint-checker
+ :name 'quote-section
+ :description "Report obsolete QUOTE section"
+ :categories '(obsolete)
+ :trust 'low)
+ (make-org-lint-checker
+ :name 'file-application
+ :description "Report obsolete \"file+application\" link"
+ :categories '(link obsolete))
+ (make-org-lint-checker
+ :name 'empty-headline-with-tags
+ :description "Report ambiguous empty headlines with tags"
+ :categories '(headline)
+ :trust 'low))
+ "List of all available checkers.")
+
+(defun org-lint--collect-duplicates
+ (ast type extract-key extract-position build-message)
+ "Helper function to collect duplicates in parse tree AST.
+
+EXTRACT-KEY is a function extracting key. It is called with
+a single argument: the element or object. Comparison is done
+with `equal'.
+
+EXTRACT-POSITION is a function returning position for the report.
+It is called with two arguments, the object or element, and the
+key.
+
+BUILD-MESSAGE is a function creating the report message. It is
+called with one argument, the key used for comparison."
+ (let* (keys
+ originals
+ reports
+ (make-report
+ (lambda (position value)
+ (push (list position (funcall build-message value)) reports))))
+ (org-element-map ast type
+ (lambda (datum)
+ (let ((key (funcall extract-key datum)))
+ (cond
+ ((not key))
+ ((assoc key keys) (cl-pushnew (assoc key keys) originals)
+ (funcall make-report (funcall extract-position datum key) key))
+ (t (push (cons key (funcall extract-position datum key)) keys))))))
+ (dolist (e originals reports) (funcall make-report (cdr e) (car e)))))
+
+(defun org-lint-duplicate-custom-id (ast)
+ (org-lint--collect-duplicates
+ ast
+ 'node-property
+ (lambda (property)
+ (and (eq (compare-strings "CUSTOM_ID" nil nil
+ (org-element-property :key property) nil nil
+ t)
+ t)
+ (org-element-property :value property)))
+ (lambda (property _) (org-element-property :begin property))
+ (lambda (key) (format "Duplicate CUSTOM_ID property \"%s\"" key))))
+
+(defun org-lint-duplicate-name (ast)
+ (org-lint--collect-duplicates
+ ast
+ org-element-all-elements
+ (lambda (datum) (org-element-property :name datum))
+ (lambda (datum name)
+ (goto-char (org-element-property :begin datum))
+ (re-search-forward
+ (format "^[ \t]*#\\+[A-Za-z]+: +%s *$" (regexp-quote name)))
+ (match-beginning 0))
+ (lambda (key) (format "Duplicate NAME \"%s\"" key))))
+
+(defun org-lint-duplicate-target (ast)
+ (org-lint--collect-duplicates
+ ast
+ 'target
+ (lambda (target) (org-split-string (org-element-property :value target)))
+ (lambda (target _) (org-element-property :begin target))
+ (lambda (key)
+ (format "Duplicate target <<%s>>" (mapconcat #'identity key " ")))))
+
+(defun org-lint-duplicate-footnote-definition (ast)
+ (org-lint--collect-duplicates
+ ast
+ 'footnote-definition
+ (lambda (definition) (org-element-property :label definition))
+ (lambda (definition _) (org-element-property :post-affiliated definition))
+ (lambda (key) (format "Duplicate footnote definition \"%s\"" key))))
+
+(defun org-lint-orphaned-affiliated-keywords (ast)
+ ;; Ignore orphan RESULTS keywords, which could be generated from
+ ;; a source block returning no value.
+ (let ((keywords (cl-set-difference org-element-affiliated-keywords
+ '("RESULT" "RESULTS")
+ :test #'equal)))
+ (org-element-map ast 'keyword
+ (lambda (k)
+ (let ((key (org-element-property :key k)))
+ (and (or (let ((case-fold-search t))
+ (string-match-p "\\`ATTR_[-_A-Za-z0-9]+\\'" key))
+ (member key keywords))
+ (list (org-element-property :post-affiliated k)
+ (format "Orphaned affiliated keyword: \"%s\"" key))))))))
+
+(defun org-lint-obsolete-affiliated-keywords (_)
+ (let ((regexp (format "^[ \t]*#\\+%s:"
+ (regexp-opt '("DATA" "LABEL" "RESNAME" "SOURCE"
+ "SRCNAME" "TBLNAME" "RESULT" "HEADERS")
+ t)))
+ reports)
+ (while (re-search-forward regexp nil t)
+ (let ((key (upcase (match-string-no-properties 1))))
+ (when (< (point)
+ (org-element-property :post-affiliated (org-element-at-point)))
+ (push
+ (list (line-beginning-position)
+ (format
+ "Obsolete affiliated keyword: \"%s\". Use \"%s\" instead"
+ key
+ (pcase key
+ ("HEADERS" "HEADER")
+ ("RESULT" "RESULTS")
+ (_ "NAME"))))
+ reports))))
+ reports))
+
+(defun org-lint-deprecated-export-blocks (ast)
+ (let ((deprecated '("ASCII" "BEAMER" "HTML" "LATEX" "MAN" "MARKDOWN" "MD"
+ "ODT" "ORG" "TEXINFO")))
+ (org-element-map ast 'special-block
+ (lambda (b)
+ (let ((type (org-element-property :type b)))
+ (when (member-ignore-case type deprecated)
+ (list
+ (org-element-property :post-affiliated b)
+ (format
+ "Deprecated syntax for export block. Use \"BEGIN_EXPORT %s\" \
+instead"
+ type))))))))
+
+(defun org-lint-deprecated-header-syntax (ast)
+ (let* ((deprecated-babel-properties
+ (mapcar (lambda (arg) (symbol-name (car arg)))
+ org-babel-common-header-args-w-values))
+ (deprecated-re
+ (format "\\`%s[ \t]" (regexp-opt deprecated-babel-properties t))))
+ (org-element-map ast '(keyword node-property)
+ (lambda (datum)
+ (let ((key (org-element-property :key datum)))
+ (pcase (org-element-type datum)
+ (`keyword
+ (let ((value (org-element-property :value datum)))
+ (and (string= key "PROPERTY")
+ (string-match deprecated-re value)
+ (list (org-element-property :begin datum)
+ (format "Deprecated syntax for \"%s\". \
+Use header-args instead"
+ (match-string-no-properties 1 value))))))
+ (`node-property
+ (and (member-ignore-case key deprecated-babel-properties)
+ (list
+ (org-element-property :begin datum)
+ (format "Deprecated syntax for \"%s\". \
+Use :header-args: instead"
+ key))))))))))
+
+(defun org-lint-missing-language-in-src-block (ast)
+ (org-element-map ast 'src-block
+ (lambda (b)
+ (unless (org-element-property :language b)
+ (list (org-element-property :post-affiliated b)
+ "Missing language in source block")))))
+
+(defun org-lint-missing-backend-in-export-block (ast)
+ (org-element-map ast 'export-block
+ (lambda (b)
+ (unless (org-element-property :type b)
+ (list (org-element-property :post-affiliated b)
+ "Missing back-end in export block")))))
+
+(defun org-lint-invalid-babel-call-block (ast)
+ (org-element-map ast 'babel-call
+ (lambda (b)
+ (cond
+ ((not (org-element-property :call b))
+ (list (org-element-property :post-affiliated b)
+ "Invalid syntax in babel call block"))
+ ((let ((h (org-element-property :end-header b)))
+ (and h (string-match-p "\\`\\[.*\\]\\'" h)))
+ (list
+ (org-element-property :post-affiliated b)
+ "Babel call's end header must not be wrapped within brackets"))))))
+
+(defun org-lint-deprecated-category-setup (ast)
+ (org-element-map ast 'keyword
+ (let (category-flag)
+ (lambda (k)
+ (cond
+ ((not (string= (org-element-property :key k) "CATEGORY")) nil)
+ (category-flag
+ (list (org-element-property :post-affiliated k)
+ "Spurious CATEGORY keyword. Set :CATEGORY: property instead"))
+ (t (setf category-flag t) nil))))))
+
+(defun org-lint-invalid-coderef-link (ast)
+ (let ((info (list :parse-tree ast)))
+ (org-element-map ast 'link
+ (lambda (link)
+ (let ((ref (org-element-property :path link)))
+ (and (equal (org-element-property :type link) "coderef")
+ (not (ignore-errors (org-export-resolve-coderef ref info)))
+ (list (org-element-property :begin link)
+ (format "Unknown coderef \"%s\"" ref))))))))
+
+(defun org-lint-invalid-custom-id-link (ast)
+ (let ((info (list :parse-tree ast)))
+ (org-element-map ast 'link
+ (lambda (link)
+ (and (equal (org-element-property :type link) "custom-id")
+ (not (ignore-errors (org-export-resolve-id-link link info)))
+ (list (org-element-property :begin link)
+ (format "Unknown custom ID \"%s\""
+ (org-element-property :path link))))))))
+
+(defun org-lint-invalid-fuzzy-link (ast)
+ (let ((info (list :parse-tree ast)))
+ (org-element-map ast 'link
+ (lambda (link)
+ (and (equal (org-element-property :type link) "fuzzy")
+ (not (ignore-errors (org-export-resolve-fuzzy-link link info)))
+ (list (org-element-property :begin link)
+ (format "Unknown fuzzy location \"%s\""
+ (let ((path (org-element-property :path link)))
+ (if (string-prefix-p "*" path)
+ (substring path 1)
+ path)))))))))
+
+(defun org-lint-invalid-id-link (ast)
+ (org-element-map ast 'link
+ (lambda (link)
+ (let ((id (org-element-property :path link)))
+ (and (equal (org-element-property :type link) "id")
+ (not (org-id-find id))
+ (list (org-element-property :begin link)
+ (format "Unknown ID \"%s\"" id)))))))
+
+(defun org-lint-special-property-in-properties-drawer (ast)
+ (org-element-map ast 'node-property
+ (lambda (p)
+ (let ((key (org-element-property :key p)))
+ (and (member-ignore-case key org-special-properties)
+ (list (org-element-property :begin p)
+ (format
+ "Special property \"%s\" found in a properties drawer"
+ key)))))))
+
+(defun org-lint-obsolete-properties-drawer (ast)
+ (org-element-map ast 'drawer
+ (lambda (d)
+ (when (equal (org-element-property :drawer-name d) "PROPERTIES")
+ (let ((section (org-element-lineage d '(section))))
+ (unless (org-element-map section 'property-drawer #'identity nil t)
+ (list (org-element-property :post-affiliated d)
+ (if (save-excursion
+ (goto-char (org-element-property :post-affiliated d))
+ (forward-line -1)
+ (or (org-at-heading-p) (org-at-planning-p)))
+ "Incorrect contents for PROPERTIES drawer"
+ "Incorrect location for PROPERTIES drawer"))))))))
+
+(defun org-lint-link-to-local-file (ast)
+ (org-element-map ast 'link
+ (lambda (l)
+ (when (equal (org-element-property :type l) "file")
+ (let ((file (org-link-unescape (org-element-property :path l))))
+ (and (not (file-remote-p file))
+ (not (file-exists-p file))
+ (list (org-element-property :begin l)
+ (format (if (org-element-lineage l '(link))
+ "Link to non-existent image file \"%s\"\
+ in link description"
+ "Link to non-existent local file \"%s\"")
+ file))))))))
+
+(defun org-lint-non-existent-setupfile-parameter (ast)
+ (org-element-map ast 'keyword
+ (lambda (k)
+ (when (equal (org-element-property :key k) "SETUPFILE")
+ (let ((file (org-unbracket-string
+ "\"" "\""
+ (org-element-property :value k))))
+ (and (not (file-remote-p file))
+ (not (file-exists-p file))
+ (list (org-element-property :begin k)
+ (format "Non-existent setup file \"%s\"" file))))))))
+
+(defun org-lint-wrong-include-link-parameter (ast)
+ (org-element-map ast 'keyword
+ (lambda (k)
+ (when (equal (org-element-property :key k) "INCLUDE")
+ (let* ((value (org-element-property :value k))
+ (path
+ (and (string-match "^\\(\".+\"\\|\\S-+\\)[ \t]*" value)
+ (save-match-data
+ (org-unbracket-string "\"" "\"" (match-string 1 value))))))
+ (if (not path)
+ (list (org-element-property :post-affiliated k)
+ "Missing location argument in INCLUDE keyword")
+ (let* ((file (org-string-nw-p
+ (if (string-match "::\\(.*\\)\\'" path)
+ (substring path 0 (match-beginning 0))
+ path)))
+ (search (and (not (equal file path))
+ (org-string-nw-p (match-string 1 path)))))
+ (if (and file
+ (not (file-remote-p file))
+ (not (file-exists-p file)))
+ (list (org-element-property :post-affiliated k)
+ "Non-existent file argument in INCLUDE keyword")
+ (let* ((visiting (if file (find-buffer-visiting file)
+ (current-buffer)))
+ (buffer (or visiting (find-file-noselect file))))
+ (unwind-protect
+ (with-current-buffer buffer
+ (when (and search
+ (not
+ (ignore-errors
+ (let ((org-link-search-inhibit-query t))
+ (org-link-search search nil t)))))
+ (list (org-element-property :post-affiliated k)
+ (format
+ "Invalid search part \"%s\" in INCLUDE keyword"
+ search))))
+ (unless visiting (kill-buffer buffer))))))))))))
+
+(defun org-lint-obsolete-include-markup (ast)
+ (let ((regexp (format "\\`\\(?:\".+\"\\|\\S-+\\)[ \t]+%s"
+ (regexp-opt
+ '("ASCII" "BEAMER" "HTML" "LATEX" "MAN" "MARKDOWN" "MD"
+ "ODT" "ORG" "TEXINFO")
+ t))))
+ (org-element-map ast 'keyword
+ (lambda (k)
+ (when (equal (org-element-property :key k) "INCLUDE")
+ (let ((case-fold-search t)
+ (value (org-element-property :value k)))
+ (when (string-match regexp value)
+ (let ((markup (match-string-no-properties 1 value)))
+ (list (org-element-property :post-affiliated k)
+ (format "Obsolete markup \"%s\" in INCLUDE keyword. \
+Use \"export %s\" instead"
+ markup
+ markup))))))))))
+
+(defun org-lint-unknown-options-item (ast)
+ (let ((allowed (delq nil
+ (append
+ (mapcar (lambda (o) (nth 2 o)) org-export-options-alist)
+ (cl-mapcan
+ (lambda (b)
+ (mapcar (lambda (o) (nth 2 o))
+ (org-export-backend-options b)))
+ org-export-registered-backends))))
+ reports)
+ (org-element-map ast 'keyword
+ (lambda (k)
+ (when (string= (org-element-property :key k) "OPTIONS")
+ (let ((value (org-element-property :value k))
+ (start 0))
+ (while (string-match "\\(.+?\\):\\((.*?)\\|\\S-*\\)[ \t]*"
+ value
+ start)
+ (setf start (match-end 0))
+ (let ((item (match-string 1 value)))
+ (unless (member item allowed)
+ (push (list (org-element-property :post-affiliated k)
+ (format "Unknown OPTIONS item \"%s\"" item))
+ reports))))))))
+ reports))
+
+(defun org-lint-invalid-macro-argument-and-template (ast)
+ (let ((extract-placeholders
+ (lambda (template)
+ (let ((start 0)
+ args)
+ (while (string-match "\\$\\([1-9][0-9]*\\)" template start)
+ (setf start (match-end 0))
+ (push (string-to-number (match-string 1 template)) args))
+ (sort (org-uniquify args) #'<))))
+ reports)
+ ;; Check arguments for macro templates.
+ (org-element-map ast 'keyword
+ (lambda (k)
+ (when (string= (org-element-property :key k) "MACRO")
+ (let* ((value (org-element-property :value k))
+ (name (and (string-match "^\\S-+" value)
+ (match-string 0 value)))
+ (template (and name
+ (org-trim (substring value (match-end 0))))))
+ (cond
+ ((not name)
+ (push (list (org-element-property :post-affiliated k)
+ "Missing name in MACRO keyword")
+ reports))
+ ((not (org-string-nw-p template))
+ (push (list (org-element-property :post-affiliated k)
+ "Missing template in macro \"%s\"" name)
+ reports))
+ (t
+ (unless (let ((args (funcall extract-placeholders template)))
+ (equal (number-sequence 1 (or (org-last args) 0)) args))
+ (push (list (org-element-property :post-affiliated k)
+ (format "Unused placeholders in macro \"%s\""
+ name))
+ reports))))))))
+ ;; Check arguments for macros.
+ (org-macro-initialize-templates)
+ (let ((templates (append
+ (mapcar (lambda (m) (cons m "$1"))
+ '("author" "date" "email" "title" "results"))
+ org-macro-templates)))
+ (org-element-map ast 'macro
+ (lambda (macro)
+ (let* ((name (org-element-property :key macro))
+ (template (cdr (assoc-string name templates t))))
+ (if (not template)
+ (push (list (org-element-property :begin macro)
+ (format "Undefined macro \"%s\"" name))
+ reports)
+ (let ((arg-numbers (funcall extract-placeholders template)))
+ (when arg-numbers
+ (let ((spurious-args
+ (nthcdr (apply #'max arg-numbers)
+ (org-element-property :args macro))))
+ (when spurious-args
+ (push
+ (list (org-element-property :begin macro)
+ (format "Unused argument%s in macro \"%s\": %s"
+ (if (> (length spurious-args) 1) "s" "")
+ name
+ (mapconcat (lambda (a) (format "\"%s\"" a))
+ spurious-args
+ ", ")))
+ reports))))))))))
+ reports))
+
+(defun org-lint-undefined-footnote-reference (ast)
+ (let ((definitions (org-element-map ast 'footnote-definition
+ (lambda (f) (org-element-property :label f)))))
+ (org-element-map ast 'footnote-reference
+ (lambda (f)
+ (let ((label (org-element-property :label f)))
+ (and label
+ (not (member label definitions))
+ (list (org-element-property :begin f)
+ (format "Missing definition for footnote [%s]"
+ label))))))))
+
+(defun org-lint-unreferenced-footnote-definition (ast)
+ (let ((references (org-element-map ast 'footnote-reference
+ (lambda (f) (org-element-property :label f)))))
+ (org-element-map ast 'footnote-definition
+ (lambda (f)
+ (let ((label (org-element-property :label f)))
+ (and label
+ (not (member label references))
+ (list (org-element-property :post-affiliated f)
+ (format "No reference for footnote definition [%s]"
+ label))))))))
+
+(defun org-lint-colon-in-name (ast)
+ (org-element-map ast org-element-all-elements
+ (lambda (e)
+ (let ((name (org-element-property :name e)))
+ (and name
+ (string-match-p ":" name)
+ (list (progn
+ (goto-char (org-element-property :begin e))
+ (re-search-forward
+ (format "^[ \t]*#\\+\\w+: +%s *$" (regexp-quote name)))
+ (match-beginning 0))
+ (format
+ "Name \"%s\" contains a colon; Babel cannot use it as input"
+ name)))))))
+
+(defun org-lint-misplaced-planning-info (_)
+ (let ((case-fold-search t)
+ reports)
+ (while (re-search-forward org-planning-line-re nil t)
+ (unless (memq (org-element-type (org-element-at-point))
+ '(comment-block example-block export-block planning
+ src-block verse-block))
+ (push (list (line-beginning-position) "Misplaced planning info line")
+ reports)))
+ reports))
+
+(defun org-lint-incomplete-drawer (_)
+ (let (reports)
+ (while (re-search-forward org-drawer-regexp nil t)
+ (let ((name (org-trim (match-string-no-properties 0)))
+ (element (org-element-at-point)))
+ (pcase (org-element-type element)
+ ((or `drawer `property-drawer)
+ (goto-char (org-element-property :end element))
+ nil)
+ ((or `comment-block `example-block `export-block `src-block
+ `verse-block)
+ nil)
+ (_
+ (push (list (line-beginning-position)
+ (format "Possible incomplete drawer \"%s\"" name))
+ reports)))))
+ reports))
+
+(defun org-lint-indented-diary-sexp (_)
+ (let (reports)
+ (while (re-search-forward "^[ \t]+%%(" nil t)
+ (unless (memq (org-element-type (org-element-at-point))
+ '(comment-block diary-sexp example-block export-block
+ src-block verse-block))
+ (push (list (line-beginning-position) "Possible indented diary-sexp")
+ reports)))
+ reports))
+
+(defun org-lint-invalid-block (_)
+ (let ((case-fold-search t)
+ (regexp "^[ \t]*#\\+\\(BEGIN\\|END\\)\\(?::\\|_[^[:space:]]*\\)?[ \t]*")
+ reports)
+ (while (re-search-forward regexp nil t)
+ (let ((name (org-trim (buffer-substring-no-properties
+ (line-beginning-position) (line-end-position)))))
+ (cond
+ ((and (string-prefix-p "END" (match-string 1) t)
+ (not (eolp)))
+ (push (list (line-beginning-position)
+ (format "Invalid block closing line \"%s\"" name))
+ reports))
+ ((not (memq (org-element-type (org-element-at-point))
+ '(center-block comment-block dynamic-block example-block
+ export-block quote-block special-block
+ src-block verse-block)))
+ (push (list (line-beginning-position)
+ (format "Possible incomplete block \"%s\""
+ name))
+ reports)))))
+ reports))
+
+(defun org-lint-invalid-keyword-syntax (_)
+ (let ((regexp "^[ \t]*#\\+\\([^[:space:]:]*\\)\\(?: \\|$\\)")
+ (exception-re
+ (format "[ \t]*#\\+%s\\(\\[.*\\]\\)?:\\(?: \\|$\\)"
+ (regexp-opt org-element-dual-keywords)))
+ reports)
+ (while (re-search-forward regexp nil t)
+ (let ((name (match-string-no-properties 1)))
+ (unless (or (string-prefix-p "BEGIN" name t)
+ (string-prefix-p "END" name t)
+ (save-excursion
+ (beginning-of-line)
+ (let ((case-fold-search t)) (looking-at exception-re))))
+ (push (list (match-beginning 0)
+ (format "Possible missing colon in keyword \"%s\"" name))
+ reports))))
+ reports))
+
+(defun org-lint-extraneous-element-in-footnote-section (ast)
+ (org-element-map ast 'headline
+ (lambda (h)
+ (and (org-element-property :footnote-section-p h)
+ (org-element-map (org-element-contents h)
+ (cl-remove-if
+ (lambda (e)
+ (memq e '(comment comment-block footnote-definition
+ property-drawer section)))
+ org-element-all-elements)
+ (lambda (e)
+ (not (and (eq (org-element-type e) 'headline)
+ (org-element-property :commentedp e))))
+ nil t '(footnote-definition property-drawer))
+ (list (org-element-property :begin h)
+ "Extraneous elements in footnote section are not exported")))))
+
+(defun org-lint-quote-section (ast)
+ (org-element-map ast '(headline inlinetask)
+ (lambda (h)
+ (let ((title (org-element-property :raw-value h)))
+ (and (or (string-prefix-p "QUOTE " title)
+ (string-prefix-p (concat org-comment-string " QUOTE ") title))
+ (list (org-element-property :begin h)
+ "Deprecated QUOTE section"))))))
+
+(defun org-lint-file-application (ast)
+ (org-element-map ast 'link
+ (lambda (l)
+ (let ((app (org-element-property :application l)))
+ (and app
+ (list (org-element-property :begin l)
+ (format "Deprecated \"file+%s\" link type" app)))))))
+
+(defun org-lint-wrong-header-argument (ast)
+ (let* ((reports)
+ (verify
+ (lambda (datum language headers)
+ (let ((allowed
+ ;; If LANGUAGE is specified, restrict allowed
+ ;; headers to both LANGUAGE-specific and default
+ ;; ones. Otherwise, accept headers from any loaded
+ ;; language.
+ (append
+ org-babel-header-arg-names
+ (cl-mapcan
+ (lambda (l)
+ (let ((v (intern (format "org-babel-header-args:%s" l))))
+ (and (boundp v) (mapcar #'car (symbol-value v)))))
+ (if language (list language)
+ (mapcar #'car org-babel-load-languages))))))
+ (dolist (header headers)
+ (let ((h (symbol-name (car header)))
+ (p (or (org-element-property :post-affiliated datum)
+ (org-element-property :begin datum))))
+ (cond
+ ((not (string-prefix-p ":" h))
+ (push
+ (list p
+ (format "Missing colon in header argument \"%s\"" h))
+ reports))
+ ((assoc-string (substring h 1) allowed))
+ (t (push (list p (format "Unknown header argument \"%s\"" h))
+ reports)))))))))
+ (org-element-map ast '(babel-call inline-babel-call inline-src-block keyword
+ node-property src-block)
+ (lambda (datum)
+ (pcase (org-element-type datum)
+ ((or `babel-call `inline-babel-call)
+ (funcall verify
+ datum
+ nil
+ (cl-mapcan #'org-babel-parse-header-arguments
+ (list
+ (org-element-property :inside-header datum)
+ (org-element-property :end-header datum)))))
+ (`inline-src-block
+ (funcall verify
+ datum
+ (org-element-property :language datum)
+ (org-babel-parse-header-arguments
+ (org-element-property :parameters datum))))
+ (`keyword
+ (when (string= (org-element-property :key datum) "PROPERTY")
+ (let ((value (org-element-property :value datum)))
+ (when (string-match "\\`header-args\\(?::\\(\\S-+\\)\\)?\\+? *"
+ value)
+ (funcall verify
+ datum
+ (match-string 1 value)
+ (org-babel-parse-header-arguments
+ (substring value (match-end 0))))))))
+ (`node-property
+ (let ((key (org-element-property :key datum)))
+ (when (let ((case-fold-search t))
+ (string-match "\\`HEADER-ARGS\\(?::\\(\\S-+\\)\\)?\\+?"
+ key))
+ (funcall verify
+ datum
+ (match-string 1 key)
+ (org-babel-parse-header-arguments
+ (org-element-property :value datum))))))
+ (`src-block
+ (funcall verify
+ datum
+ (org-element-property :language datum)
+ (cl-mapcan #'org-babel-parse-header-arguments
+ (cons (org-element-property :parameters datum)
+ (org-element-property :header datum))))))))
+ reports))
+
+(defun org-lint-wrong-header-value (ast)
+ (let (reports)
+ (org-element-map ast
+ '(babel-call inline-babel-call inline-src-block src-block)
+ (lambda (datum)
+ (let* ((type (org-element-type datum))
+ (language (org-element-property :language datum))
+ (allowed-header-values
+ (append (and language
+ (let ((v (intern (concat "org-babel-header-args:"
+ language))))
+ (and (boundp v) (symbol-value v))))
+ org-babel-common-header-args-w-values))
+ (datum-header-values
+ (org-babel-parse-header-arguments
+ (org-trim
+ (pcase type
+ (`src-block
+ (mapconcat
+ #'identity
+ (cons (org-element-property :parameters datum)
+ (org-element-property :header datum))
+ " "))
+ (`inline-src-block
+ (or (org-element-property :parameters datum) ""))
+ (_
+ (concat
+ (org-element-property :inside-header datum)
+ " "
+ (org-element-property :end-header datum))))))))
+ (dolist (header datum-header-values)
+ (let ((allowed-values
+ (cdr (assoc-string (substring (symbol-name (car header)) 1)
+ allowed-header-values))))
+ (unless (memq allowed-values '(:any nil))
+ (let ((values (cdr header))
+ groups-alist)
+ (dolist (v (if (stringp values) (org-split-string values)
+ (list values)))
+ (let ((valid-value nil))
+ (catch 'exit
+ (dolist (group allowed-values)
+ (cond
+ ((not (funcall
+ (if (stringp v) #'assoc-string #'assoc)
+ v group))
+ (when (memq :any group)
+ (setf valid-value t)
+ (push (cons group v) groups-alist)))
+ ((assq group groups-alist)
+ (push
+ (list
+ (or (org-element-property :post-affiliated datum)
+ (org-element-property :begin datum))
+ (format
+ "Forbidden combination in header \"%s\": %s, %s"
+ (car header)
+ (cdr (assq group groups-alist))
+ v))
+ reports)
+ (throw 'exit nil))
+ (t (push (cons group v) groups-alist)
+ (setf valid-value t))))
+ (unless valid-value
+ (push
+ (list
+ (or (org-element-property :post-affiliated datum)
+ (org-element-property :begin datum))
+ (format "Unknown value \"%s\" for header \"%s\""
+ v
+ (car header)))
+ reports))))))))))))
+ reports))
+
+(defun org-lint-empty-headline-with-tags (ast)
+ (org-element-map ast '(headline inlinetask)
+ (lambda (h)
+ (let ((title (org-element-property :raw-value h)))
+ (and (string-match-p "\\`:[[:alnum:]_@#%:]+:\\'" title)
+ (list (org-element-property :begin h)
+ (format "Headline containing only tags is ambiguous: %S"
+ title)))))))
+
+
+;;; Reports UI
+
+(defvar org-lint--report-mode-map
+ (let ((map (make-sparse-keymap)))
+ (set-keymap-parent map tabulated-list-mode-map)
+ (define-key map (kbd "RET") 'org-lint--jump-to-source)
+ (define-key map (kbd "TAB") 'org-lint--show-source)
+ (define-key map (kbd "C-j") 'org-lint--show-source)
+ (define-key map (kbd "h") 'org-lint--hide-checker)
+ (define-key map (kbd "i") 'org-lint--ignore-checker)
+ map)
+ "Local keymap for `org-lint--report-mode' buffers.")
+
+(define-derived-mode org-lint--report-mode tabulated-list-mode "OrgLint"
+ "Major mode used to display reports emitted during linting.
+\\{org-lint--report-mode-map}"
+ (setf tabulated-list-format
+ `[("Line" 6
+ (lambda (a b)
+ (< (string-to-number (aref (cadr a) 0))
+ (string-to-number (aref (cadr b) 0))))
+ :right-align t)
+ ("Trust" 5 t)
+ ("Warning" 0 t)])
+ (tabulated-list-init-header))
+
+(defun org-lint--generate-reports (buffer checkers)
+ "Generate linting report for BUFFER.
+
+CHECKERS is the list of checkers used.
+
+Return an alist (ID [LINE TRUST DESCRIPTION CHECKER]), suitable
+for `tabulated-list-printer'."
+ (with-current-buffer buffer
+ (save-excursion
+ (goto-char (point-min))
+ (let ((ast (org-element-parse-buffer))
+ (id 0)
+ (last-line 1)
+ (last-pos 1))
+ ;; Insert unique ID for each report. Replace buffer positions
+ ;; with line numbers.
+ (mapcar
+ (lambda (report)
+ (list
+ (cl-incf id)
+ (apply #'vector
+ (cons
+ (progn
+ (goto-char (car report))
+ (beginning-of-line)
+ (prog1 (number-to-string
+ (cl-incf last-line
+ (count-lines last-pos (point))))
+ (setf last-pos (point))))
+ (cdr report)))))
+ ;; Insert trust level in generated reports. Also sort them
+ ;; by buffer position in order to optimize lines computation.
+ (sort (cl-mapcan
+ (lambda (c)
+ (let ((trust (symbol-name (org-lint-checker-trust c))))
+ (mapcar
+ (lambda (report)
+ (list (car report) trust (nth 1 report) c))
+ (save-excursion
+ (funcall
+ (intern (format "org-lint-%s"
+ (org-lint-checker-name c)))
+ ast)))))
+ checkers)
+ #'car-less-than-car))))))
+
+(defvar-local org-lint--source-buffer nil
+ "Source buffer associated to current report buffer.")
+
+(defvar-local org-lint--local-checkers nil
+ "List of checkers used to build current report.")
+
+(defun org-lint--refresh-reports ()
+ (setq tabulated-list-entries
+ (org-lint--generate-reports org-lint--source-buffer
+ org-lint--local-checkers))
+ (tabulated-list-print))
+
+(defun org-lint--current-line ()
+ "Return current report line, as a number."
+ (string-to-number (aref (tabulated-list-get-entry) 0)))
+
+(defun org-lint--current-checker (&optional entry)
+ "Return current report checker.
+When optional argument ENTRY is non-nil, use this entry instead
+of current one."
+ (aref (if entry (nth 1 entry) (tabulated-list-get-entry)) 3))
+
+(defun org-lint--display-reports (source checkers)
+ "Display linting reports for buffer SOURCE.
+CHECKERS is the list of checkers used."
+ (let ((buffer (get-buffer-create "*Org Lint*")))
+ (with-current-buffer buffer
+ (org-lint--report-mode)
+ (setf org-lint--source-buffer source)
+ (setf org-lint--local-checkers checkers)
+ (org-lint--refresh-reports)
+ (tabulated-list-print)
+ (add-hook 'tabulated-list-revert-hook #'org-lint--refresh-reports nil t))
+ (pop-to-buffer buffer)))
+
+(defun org-lint--jump-to-source ()
+ "Move to source line that generated the report at point."
+ (interactive)
+ (let ((l (org-lint--current-line)))
+ (switch-to-buffer-other-window org-lint--source-buffer)
+ (org-goto-line l)
+ (org-show-set-visibility 'local)
+ (recenter)))
+
+(defun org-lint--show-source ()
+ "Show source line that generated the report at point."
+ (interactive)
+ (let ((buffer (current-buffer)))
+ (org-lint--jump-to-source)
+ (switch-to-buffer-other-window buffer)))
+
+(defun org-lint--hide-checker ()
+ "Hide all reports from checker that generated the report at point."
+ (interactive)
+ (let ((c (org-lint--current-checker)))
+ (setf tabulated-list-entries
+ (cl-remove-if (lambda (e) (equal c (org-lint--current-checker e)))
+ tabulated-list-entries))
+ (tabulated-list-print)))
+
+(defun org-lint--ignore-checker ()
+ "Ignore all reports from checker that generated the report at point.
+Checker will also be ignored in all subsequent reports."
+ (interactive)
+ (setf org-lint--local-checkers
+ (remove (org-lint--current-checker) org-lint--local-checkers))
+ (org-lint--hide-checker))
+
+
+;;; Public function
+
+;;;###autoload
+(defun org-lint (&optional arg)
+ "Check current Org buffer for syntax mistakes.
+
+By default, run all checkers. With a `\\[universal-argument]' prefix ARG, \
+select one
+category of checkers only. With a `\\[universal-argument] \
+\\[universal-argument]' prefix, run one precise
+checker by its name.
+
+ARG can also be a list of checker names, as symbols, to run."
+ (interactive "P")
+ (unless (derived-mode-p 'org-mode) (user-error "Not in an Org buffer"))
+ (when (called-interactively-p 'any)
+ (message "Org linting process starting..."))
+ (let ((checkers
+ (pcase arg
+ (`nil org-lint--checkers)
+ (`(4)
+ (let ((category
+ (completing-read
+ "Checker category: "
+ (mapcar #'org-lint-checker-categories org-lint--checkers)
+ nil t)))
+ (cl-remove-if-not
+ (lambda (c)
+ (assoc-string (org-lint-checker-categories c) category))
+ org-lint--checkers)))
+ (`(16)
+ (list
+ (let ((name (completing-read
+ "Checker name: "
+ (mapcar #'org-lint-checker-name org-lint--checkers)
+ nil t)))
+ (catch 'exit
+ (dolist (c org-lint--checkers)
+ (when (string= (org-lint-checker-name c) name)
+ (throw 'exit c)))))))
+ ((pred consp)
+ (cl-remove-if-not (lambda (c) (memq (org-lint-checker-name c) arg))
+ org-lint--checkers))
+ (_ (user-error "Invalid argument `%S' for `org-lint'" arg)))))
+ (if (not (called-interactively-p 'any))
+ (org-lint--generate-reports (current-buffer) checkers)
+ (org-lint--display-reports (current-buffer) checkers)
+ (message "Org linting process completed"))))
+
+
+(provide 'org-lint)
+;;; org-lint.el ends here
diff --git a/lisp/org/org-list.el b/lisp/org/org-list.el
index 4a45fd9817a..a3e26256f9b 100644
--- a/lisp/org/org-list.el
+++ b/lisp/org/org-list.el
@@ -1,4 +1,4 @@
-;;; org-list.el --- Plain lists for Org-mode
+;;; org-list.el --- Plain lists for Org -*- lexical-binding: t; -*-
;;
;; Copyright (C) 2004-2017 Free Software Foundation, Inc.
;;
@@ -25,7 +25,7 @@
;;
;;; Commentary:
-;; This file contains the code dealing with plain lists in Org-mode.
+;; This file contains the code dealing with plain lists in Org mode.
;; The core concept behind lists is their structure. A structure is
;; a snapshot of the list, in the shape of a data tree (see
@@ -76,8 +76,7 @@
;;; Code:
-(eval-when-compile
- (require 'cl))
+(require 'cl-lib)
(require 'org-macs)
(require 'org-compat)
@@ -88,59 +87,84 @@
(defvar org-closed-string)
(defvar org-deadline-string)
(defvar org-description-max-indent)
-(defvar org-drawers)
+(defvar org-done-keywords)
+(defvar org-drawer-regexp)
+(defvar org-element-all-objects)
+(defvar org-inhibit-startup)
(defvar org-odd-levels-only)
+(defvar org-outline-regexp-bol)
(defvar org-scheduled-string)
+(defvar org-todo-line-regexp)
(defvar org-ts-regexp)
(defvar org-ts-regexp-both)
-(declare-function outline-invisible-p "outline" (&optional pos))
-(declare-function outline-flag-region "outline" (from to flag))
-(declare-function outline-next-heading "outline" ())
-(declare-function outline-previous-heading "outline" ())
-
-(declare-function org-at-heading-p "org" (&optional ignored))
-(declare-function org-before-first-heading-p "org" ())
+(declare-function org-at-heading-p "org" (&optional invisible-ok))
(declare-function org-back-to-heading "org" (&optional invisible-ok))
+(declare-function org-before-first-heading-p "org" ())
(declare-function org-combine-plists "org" (&rest plists))
-(declare-function org-count "org" (cl-item cl-seq))
(declare-function org-current-level "org" ())
+(declare-function org-element-at-point "org-element" ())
+(declare-function org-element-context "org-element" (&optional element))
+(declare-function org-element-interpret-data "org-element" (data))
+(declare-function
+ org-element-lineage "org-element" (blob &optional types with-self))
+(declare-function org-element-macro-interpreter "org-element" (macro ##))
+(declare-function
+ org-element-map "org-element"
+ (data types fun &optional info first-match no-recursion with-affiliated))
+(declare-function org-element-normalize-string "org-element" (s))
+(declare-function org-element-parse-buffer "org-element"
+ (&optional granularity visible-only))
+(declare-function org-element-property "org-element" (property element))
+(declare-function org-element-put-property "org-element"
+ (element property value))
+(declare-function org-element-set-element "org-element" (old new))
+(declare-function org-element-type "org-element" (element))
+(declare-function org-element-update-syntax "org-element" ())
(declare-function org-entry-get "org"
(pom property &optional inherit literal-nil))
+(declare-function org-export-create-backend "ox" (&rest rest) t)
+(declare-function org-export-data-with-backend "ox" (data backend info))
+(declare-function org-export-get-backend "ox" (name))
+(declare-function org-export-get-environment "ox"
+ (&optional backend subtreep ext-plist))
+(declare-function org-export-get-next-element "ox"
+ (blob info &optional n))
+(declare-function org-export-with-backend "ox"
+ (backend data &optional contents info))
(declare-function org-fix-tags-on-the-fly "org" ())
(declare-function org-get-indentation "org" (&optional line))
-(declare-function org-icompleting-read "org" (&rest args))
+(declare-function org-get-todo-state "org" ())
(declare-function org-in-block-p "org" (names))
(declare-function org-in-regexp "org" (re &optional nlines visually))
+(declare-function org-inlinetask-goto-beginning "org-inlinetask" ())
+(declare-function org-inlinetask-goto-end "org-inlinetask" ())
+(declare-function org-inlinetask-in-task-p "org-inlinetask" ())
+(declare-function org-inlinetask-outline-regexp "org-inlinetask" ())
(declare-function org-level-increment "org" ())
(declare-function org-narrow-to-subtree "org" ())
-(declare-function org-at-heading-p "org" (&optional invisible-ok))
-(declare-function org-previous-line-empty-p "org" (&optional next))
-(declare-function org-remove-if "org" (predicate seq))
+(declare-function org-outline-level "org" ())
+(declare-function org-previous-line-empty-p "org" ())
(declare-function org-reduced-level "org" (L))
+(declare-function org-remove-indentation "org" (code &optional n))
(declare-function org-show-subtree "org" ())
(declare-function org-sort-remove-invisible "org" (S))
(declare-function org-time-string-to-seconds "org" (s))
(declare-function org-timer-hms-to-secs "org-timer" (hms))
(declare-function org-timer-item "org-timer" (&optional arg))
-(declare-function org-trim "org" (s))
+(declare-function org-trim "org" (s &optional keep-lead))
(declare-function org-uniquify "org" (list))
-
-(declare-function org-inlinetask-goto-beginning "org-inlinetask" ())
-(declare-function org-inlinetask-goto-end "org-inlinetask" ())
-(declare-function org-inlinetask-in-task-p "org-inlinetask" ())
-(declare-function org-inlinetask-outline-regexp "org-inlinetask" ())
-
-(declare-function org-export-string-as "ox"
- (string backend &optional body-only ext-plist))
-
+(declare-function org-invisible-p "org" (&optional pos))
+(declare-function outline-flag-region "outline" (from to flag))
+(declare-function outline-next-heading "outline" ())
+(declare-function outline-previous-heading "outline" ())
;;; Configuration variables
(defgroup org-plain-lists nil
- "Options concerning plain lists in Org-mode."
+ "Options concerning plain lists in Org mode."
:tag "Org Plain lists"
:group 'org-structure)
@@ -211,14 +235,20 @@ into
(defcustom org-plain-list-ordered-item-terminator t
"The character that makes a line with leading number an ordered list item.
-Valid values are ?. and ?\). To get both terminators, use t."
+Valid values are ?. and ?\). To get both terminators, use t.
+
+This variable needs to be set before org.el is loaded. If you
+need to make a change while Emacs is running, use the customize
+interface or run the following code after updating it:
+
+ `\\[org-element-update-syntax]'"
:group 'org-plain-lists
:type '(choice (const :tag "dot like in \"2.\"" ?.)
(const :tag "paren like in \"2)\"" ?\))
- (const :tag "both" t)))
+ (const :tag "both" t))
+ :set (lambda (var val) (set var val)
+ (when (featurep 'org-element) (org-element-update-syntax))))
-(define-obsolete-variable-alias 'org-alphabetical-lists
- 'org-list-allow-alphabetical "24.4") ; Since 8.0
(defcustom org-list-allow-alphabetical nil
"Non-nil means single character alphabetical bullets are allowed.
@@ -230,13 +260,12 @@ This variable needs to be set before org.el is loaded. If you
need to make a change while Emacs is running, use the customize
interface or run the following code after updating it:
- (when (featurep \\='org-element) (load \"org-element\" t t))"
+ `\\[org-element-update-syntax]'"
:group 'org-plain-lists
:version "24.1"
:type 'boolean
- :set (lambda (var val)
- (when (featurep 'org-element) (load "org-element" t t))
- (set var val)))
+ :set (lambda (var val) (set var val)
+ (when (featurep 'org-element) (org-element-update-syntax))))
(defcustom org-list-two-spaces-after-bullet-regexp nil
"A regular expression matching bullets that should have 2 spaces after them.
@@ -250,23 +279,22 @@ spaces instead of one after the bullet in each item of the list."
(const :tag "never" nil)
(regexp)))
-(define-obsolete-variable-alias 'org-empty-line-terminates-plain-lists
- 'org-list-empty-line-terminates-plain-lists "24.4") ;; Since 8.0
-(defcustom org-list-empty-line-terminates-plain-lists nil
- "Non-nil means an empty line ends all plain list levels.
-Otherwise, two of them will be necessary."
- :group 'org-plain-lists
- :type 'boolean)
-
(defcustom org-list-automatic-rules '((checkbox . t)
(indent . t))
"Non-nil means apply set of rules when acting on lists.
+\\
By default, automatic actions are taken when using
- \\[org-meta-return], \\[org-metaright], \\[org-metaleft],
- \\[org-shiftmetaright], \\[org-shiftmetaleft],
- \\[org-ctrl-c-minus], \\[org-toggle-checkbox] or
- \\[org-insert-todo-heading]. You can disable individually these
- rules by setting them to nil. Valid rules are:
+ `\\[org-meta-return]',
+ `\\[org-metaright]',
+ `\\[org-metaleft]',
+ `\\[org-shiftmetaright]',
+ `\\[org-shiftmetaleft]',
+ `\\[org-ctrl-c-minus]',
+ `\\[org-toggle-checkbox]',
+ `\\[org-insert-todo-heading]'.
+
+You can disable individually these rules by setting them to nil.
+Valid rules are:
checkbox when non-nil, checkbox statistics is updated each time
you either insert a new checkbox or toggle a checkbox.
@@ -286,13 +314,15 @@ indent when non-nil, indenting or outdenting list top-item
(defcustom org-list-use-circular-motion nil
"Non-nil means commands implying motion in lists should be cyclic.
-
+\\
In that case, the item following the last item is the first one,
and the item preceding the first item is the last one.
-This affects the behavior of \\[org-move-item-up],
- \\[org-move-item-down], \\[org-next-item] and
- \\[org-previous-item]."
+This affects the behavior of
+ `\\[org-move-item-up]',
+ `\\[org-move-item-down]',
+ `\\[org-next-item]',
+ `\\[org-previous-item]'."
:group 'org-plain-lists
:version "24.1"
:type 'boolean)
@@ -304,8 +334,6 @@ This hook runs even if checkbox rule in
implement alternative ways of collecting statistics
information.")
-(define-obsolete-variable-alias 'org-hierarchical-checkbox-statistics
- 'org-checkbox-hierarchical-statistics "24.4") ;; Since 8.0
(defcustom org-checkbox-hierarchical-statistics t
"Non-nil means checkbox statistics counts only the state of direct children.
When nil, all boxes below the cookie are counted.
@@ -314,8 +342,6 @@ with the word \"recursive\" in the value."
:group 'org-plain-lists
:type 'boolean)
-(org-defvaralias 'org-description-max-indent
- 'org-list-description-max-indent) ;; Since 8.0
(defcustom org-list-description-max-indent 20
"Maximum indentation for the second line of a description list.
When the indentation would be larger than this, it will become
@@ -358,8 +384,7 @@ list, obtained by prompting the user."
(list (symbol :tag "Major mode")
(string :tag "Format"))))
-(defvar org-list-forbidden-blocks '("example" "verse" "src" "ascii" "beamer"
- "html" "latex" "odt")
+(defvar org-list-forbidden-blocks '("example" "verse" "src" "export")
"Names of blocks where lists are not allowed.
Names must be in lower case.")
@@ -374,10 +399,8 @@ specifically, type `block' is determined by the variable
;;; Predicates and regexps
-(defconst org-list-end-re (if org-list-empty-line-terminates-plain-lists "^[ \t]*\n"
- "^[ \t]*\n[ \t]*\n")
- "Regex corresponding to the end of a list.
-It depends on `org-list-empty-line-terminates-plain-lists'.")
+(defconst org-list-end-re "^[ \t]*\n[ \t]*\n"
+ "Regex matching the end of a plain list.")
(defconst org-list-full-item-re
(concat "^[ \t]*\\(\\(?:[-+*]\\|\\(?:[0-9]+\\|[A-Za-z]\\)[.)]\\)\\(?:[ \t]+\\|$\\)\\)"
@@ -430,9 +453,6 @@ group 4: description tag")
(let* ((case-fold-search t)
(context (org-list-context))
(lim-up (car context))
- (drawers-re (concat "^[ \t]*:\\("
- (mapconcat #'regexp-quote org-drawers "\\|")
- "\\):[ \t]*$"))
(inlinetask-re (and (featurep 'org-inlinetask)
(org-inlinetask-outline-regexp)))
(item-re (org-item-re))
@@ -476,7 +496,7 @@ group 4: description tag")
((and (looking-at "^[ \t]*#\\+end_")
(re-search-backward "^[ \t]*#\\+begin_" lim-up t)))
((and (looking-at "^[ \t]*:END:")
- (re-search-backward drawers-re lim-up t))
+ (re-search-backward org-drawer-regexp lim-up t))
(beginning-of-line))
((and inlinetask-re (looking-at inlinetask-re))
(org-inlinetask-goto-beginning)
@@ -547,11 +567,7 @@ Contexts `block' and `invalid' refer to `org-list-forbidden-blocks'."
(lim-down (or (save-excursion (outline-next-heading)) (point-max))))
;; Is point inside a drawer?
(let ((end-re "^[ \t]*:END:")
- ;; Can't use org-drawers-regexp as this function might
- ;; be called in buffers not in Org mode.
- (beg-re (concat "^[ \t]*:\\("
- (mapconcat #'regexp-quote org-drawers "\\|")
- "\\):[ \t]*$")))
+ (beg-re org-drawer-regexp))
(when (save-excursion
(and (not (looking-at beg-re))
(not (looking-at end-re))
@@ -635,9 +651,6 @@ Assume point is at an item."
(lim-down (nth 1 context))
(text-min-ind 10000)
(item-re (org-item-re))
- (drawers-re (concat "^[ \t]*:\\("
- (mapconcat #'regexp-quote org-drawers "\\|")
- "\\):[ \t]*$"))
(inlinetask-re (and (featurep 'org-inlinetask)
(org-inlinetask-outline-regexp)))
(beg-cell (cons (point) (org-get-indentation)))
@@ -654,7 +667,7 @@ Assume point is at an item."
(match-string-no-properties 2) ; counter
(match-string-no-properties 3) ; checkbox
;; Description tag.
- (and (save-match-data (string-match "[-+*]" bullet))
+ (and (string-match-p "[-+*]" bullet)
(match-string-no-properties 4)))))))
(end-before-blank
(function
@@ -700,7 +713,7 @@ Assume point is at an item."
((and (looking-at "^[ \t]*#\\+end_")
(re-search-backward "^[ \t]*#\\+begin_" lim-up t)))
((and (looking-at "^[ \t]*:END:")
- (re-search-backward drawers-re lim-up t))
+ (re-search-backward org-drawer-regexp lim-up t))
(beginning-of-line))
((and inlinetask-re (looking-at inlinetask-re))
(org-inlinetask-goto-beginning)
@@ -766,7 +779,7 @@ Assume point is at an item."
(cond
((and (looking-at "^[ \t]*#\\+begin_")
(re-search-forward "^[ \t]*#\\+end_" lim-down t)))
- ((and (looking-at drawers-re)
+ ((and (looking-at org-drawer-regexp)
(re-search-forward "^[ \t]*:END:" lim-down t))))
(forward-line 1))))))
(setq struct (append itm-lst (cdr (nreverse itm-lst-2)))
@@ -1021,7 +1034,7 @@ Possible types are `descriptive', `ordered' and `unordered'. The
type is determined by the first item of the list."
(let ((first (org-list-get-list-begin item struct prevs)))
(cond
- ((string-match "[[:alnum:]]" (org-list-get-bullet first struct)) 'ordered)
+ ((string-match-p "[[:alnum:]]" (org-list-get-bullet first struct)) 'ordered)
((org-list-get-tag first struct) 'descriptive)
(t 'unordered))))
@@ -1043,7 +1056,7 @@ that value."
(let ((seq 0) (pos item) counter)
(while (and (not (setq counter (org-list-get-counter pos struct)))
(setq pos (org-list-get-prev-item pos struct prevs)))
- (incf seq))
+ (cl-incf seq))
(if (not counter) (1+ seq)
(cond
((string-match "[A-Za-z]" counter)
@@ -1137,13 +1150,20 @@ This function modifies STRUCT."
;; Store overlays responsible for visibility status. We
;; also need to store their boundaries as they will be
;; removed from buffer.
- (overlays (cons
- (mapcar (lambda (ov)
- (list ov (overlay-start ov) (overlay-end ov)))
- (overlays-in beg-A end-A))
- (mapcar (lambda (ov)
- (list ov (overlay-start ov) (overlay-end ov)))
- (overlays-in beg-B end-B)))))
+ (overlays
+ (cons
+ (delq nil
+ (mapcar (lambda (o)
+ (and (>= (overlay-start o) beg-A)
+ (<= (overlay-end o) end-A)
+ (list o (overlay-start o) (overlay-end o))))
+ (overlays-in beg-A end-A)))
+ (delq nil
+ (mapcar (lambda (o)
+ (and (>= (overlay-start o) beg-B)
+ (<= (overlay-end o) end-B)
+ (list o (overlay-start o) (overlay-end o))))
+ (overlays-in beg-B end-B))))))
;; 1. Move effectively items in buffer.
(goto-char beg-A)
(delete-region beg-A end-B-no-blank)
@@ -1154,42 +1174,39 @@ This function modifies STRUCT."
;; as empty spaces are not moved there. In others words,
;; item BEG-A will end with whitespaces that were at the end
;; of BEG-B and the same applies to BEG-B.
- (mapc (lambda (e)
- (let ((pos (car e)))
- (cond
- ((< pos beg-A))
- ((memq pos sub-A)
- (let ((end-e (nth 6 e)))
- (setcar e (+ pos (- end-B-no-blank end-A-no-blank)))
- (setcar (nthcdr 6 e)
- (+ end-e (- end-B-no-blank end-A-no-blank)))
- (when (= end-e end-A) (setcar (nthcdr 6 e) end-B))))
- ((memq pos sub-B)
- (let ((end-e (nth 6 e)))
- (setcar e (- (+ pos beg-A) beg-B))
- (setcar (nthcdr 6 e) (+ end-e (- beg-A beg-B)))
- (when (= end-e end-B)
- (setcar (nthcdr 6 e)
- (+ beg-A size-B (- end-A end-A-no-blank))))))
- ((< pos beg-B)
- (let ((end-e (nth 6 e)))
- (setcar e (+ pos (- size-B size-A)))
- (setcar (nthcdr 6 e) (+ end-e (- size-B size-A))))))))
- struct)
- (setq struct (sort struct (lambda (e1 e2) (< (car e1) (car e2)))))
+ (dolist (e struct)
+ (let ((pos (car e)))
+ (cond
+ ((< pos beg-A))
+ ((memq pos sub-A)
+ (let ((end-e (nth 6 e)))
+ (setcar e (+ pos (- end-B-no-blank end-A-no-blank)))
+ (setcar (nthcdr 6 e)
+ (+ end-e (- end-B-no-blank end-A-no-blank)))
+ (when (= end-e end-A) (setcar (nthcdr 6 e) end-B))))
+ ((memq pos sub-B)
+ (let ((end-e (nth 6 e)))
+ (setcar e (- (+ pos beg-A) beg-B))
+ (setcar (nthcdr 6 e) (+ end-e (- beg-A beg-B)))
+ (when (= end-e end-B)
+ (setcar (nthcdr 6 e)
+ (+ beg-A size-B (- end-A end-A-no-blank))))))
+ ((< pos beg-B)
+ (let ((end-e (nth 6 e)))
+ (setcar e (+ pos (- size-B size-A)))
+ (setcar (nthcdr 6 e) (+ end-e (- size-B size-A))))))))
+ (setq struct (sort struct #'car-less-than-car))
;; Restore visibility status, by moving overlays to their new
;; position.
- (mapc (lambda (ov)
- (move-overlay
- (car ov)
- (+ (nth 1 ov) (- (+ beg-B (- size-B size-A)) beg-A))
- (+ (nth 2 ov) (- (+ beg-B (- size-B size-A)) beg-A))))
- (car overlays))
- (mapc (lambda (ov)
- (move-overlay (car ov)
- (+ (nth 1 ov) (- beg-A beg-B))
- (+ (nth 2 ov) (- beg-A beg-B))))
- (cdr overlays))
+ (dolist (ov (car overlays))
+ (move-overlay
+ (car ov)
+ (+ (nth 1 ov) (- (+ beg-B (- size-B size-A)) beg-A))
+ (+ (nth 2 ov) (- (+ beg-B (- size-B size-A)) beg-A))))
+ (dolist (ov (cdr overlays))
+ (move-overlay (car ov)
+ (+ (nth 1 ov) (- beg-A beg-B))
+ (+ (nth 2 ov) (- beg-A beg-B))))
;; Return structure.
struct)))
@@ -1219,7 +1236,7 @@ some heuristics to guess the result."
(point))))))))
(cond
;; Trivial cases where there should be none.
- ((or org-list-empty-line-terminates-plain-lists (not insert-blank-p)) 0)
+ ((not insert-blank-p) 0)
;; When `org-blank-before-new-entry' says so, it is 1.
((eq insert-blank-p t) 1)
;; `plain-list-item' is 'auto. Count blank lines separating
@@ -1272,12 +1289,16 @@ This function modifies STRUCT."
(beforep
(progn
(looking-at org-list-full-item-re)
- ;; Do not count tag in a non-descriptive list.
- (<= pos (if (and (match-beginning 4)
- (save-match-data
- (string-match "[.)]" (match-string 1))))
- (match-beginning 4)
- (match-end 0)))))
+ (<= pos
+ (cond
+ ((not (match-beginning 4)) (match-end 0))
+ ;; Ignore tag in a non-descriptive list.
+ ((save-match-data (string-match "[.)]" (match-string 1)))
+ (match-beginning 4))
+ (t (save-excursion
+ (goto-char (match-end 4))
+ (skip-chars-forward " \t")
+ (point)))))))
(split-line-p (org-get-alist-option org-M-RET-may-split-line 'item))
(blank-nb (org-list-separating-blank-lines-number
pos struct prevs))
@@ -1317,7 +1338,7 @@ This function modifies STRUCT."
(size-offset (- item-size (length text-cut))))
;; 4. Insert effectively item into buffer.
(goto-char item)
- (org-indent-to-column ind)
+ (indent-to-column ind)
(insert body item-sep)
;; 5. Add new item to STRUCT.
(mapc (lambda (e)
@@ -1459,7 +1480,7 @@ This function returns, destructively, the new list structure."
(save-excursion
(goto-char (org-list-get-last-item item struct prevs))
(point-at-eol)))
- ((string-match "\\`[0-9]+\\'" dest)
+ ((string-match-p "\\`[0-9]+\\'" dest)
(let* ((all (org-list-get-all-items item struct prevs))
(len (length all))
(index (mod (string-to-number dest) len)))
@@ -1473,8 +1494,10 @@ This function returns, destructively, the new list structure."
(point-at-eol)))))
(t dest)))
(org-M-RET-may-split-line nil)
- ;; Store visibility.
- (visibility (overlays-in item item-end)))
+ ;; Store inner overlays (to preserve visibility).
+ (overlays (cl-remove-if (lambda (o) (or (< (overlay-start o) item)
+ (> (overlay-end o) item)))
+ (overlays-in item item-end))))
(cond
((eq dest 'delete) (org-list-delete-item item struct))
((eq dest 'kill)
@@ -1509,13 +1532,12 @@ This function returns, destructively, the new list structure."
new-end
(+ end shift)))))))
moved-items))
- (lambda (e1 e2) (< (car e1) (car e2))))))
- ;; 2. Restore visibility.
- (mapc (lambda (ov)
- (move-overlay ov
- (+ (overlay-start ov) (- (point) item))
- (+ (overlay-end ov) (- (point) item))))
- visibility)
+ #'car-less-than-car)))
+ ;; 2. Restore inner overlays.
+ (dolist (o overlays)
+ (move-overlay o
+ (+ (overlay-start o) (- (point) item))
+ (+ (overlay-end o) (- (point) item))))
;; 3. Eventually delete extra copy of the item and clean marker.
(prog1 (org-list-delete-item (marker-position item) struct)
(move-marker item nil)))
@@ -1632,7 +1654,7 @@ as returned by `org-list-prevs-alist'."
(while item
(let ((count (org-list-get-counter item struct)))
;; Virtually determine current bullet
- (if (and count (string-match "[a-zA-Z]" count))
+ (if (and count (string-match-p "[a-zA-Z]" count))
;; Counters are not case-sensitive.
(setq ascii (string-to-char (upcase count)))
(setq ascii (1+ ascii)))
@@ -1861,10 +1883,9 @@ Initial position of cursor is restored after the changes."
(item-re (org-item-re))
(shift-body-ind
(function
- ;; Shift the indentation between END and BEG by DELTA. If
- ;; MAX-IND is non-nil, ensure that no line will be indented
- ;; more than that number. Start from the line before END.
- (lambda (end beg delta max-ind)
+ ;; Shift the indentation between END and BEG by DELTA.
+ ;; Start from the line before END.
+ (lambda (end beg delta)
(goto-char end)
(skip-chars-backward " \r\t\n")
(beginning-of-line)
@@ -1876,10 +1897,8 @@ Initial position of cursor is restored after the changes."
((and inlinetask-re (looking-at inlinetask-re))
(org-inlinetask-goto-beginning))
;; Shift only non-empty lines.
- ((org-looking-at-p "^[ \t]*\\S-")
- (let ((i (org-get-indentation)))
- (org-indent-line-to
- (if max-ind (min (+ i delta) max-ind) (+ i delta))))))
+ ((looking-at-p "^[ \t]*\\S-")
+ (indent-line-to (+ (org-get-indentation) delta))))
(forward-line -1)))))
(modify-item
(function
@@ -1934,37 +1953,53 @@ Initial position of cursor is restored after the changes."
;; belongs to: it is the last item (ITEM-UP), whose
;; ending is further than the position we're
;; interested in.
- (let ((item-up (assoc-default end-pos acc-end '>)))
+ (let ((item-up (assoc-default end-pos acc-end #'>)))
(push (cons end-pos item-up) end-list)))
(push (cons end-pos pos) acc-end)))
;; 2. Slice the items into parts that should be shifted by the
;; same amount of indentation. Each slice follow the pattern
- ;; (END BEG DELTA MAX-IND-OR-NIL). Slices are returned in
- ;; reverse order.
+ ;; (END BEG DELTA). Slices are returned in reverse order.
(setq all-ends (sort (append (mapcar #'car itm-shift)
(org-uniquify (mapcar #'car end-list)))
- '<))
+ #'<)
+ acc-end (nreverse acc-end))
(while (cdr all-ends)
(let* ((up (pop all-ends))
(down (car all-ends))
(itemp (assq up struct))
- (item (if itemp up (cdr (assq up end-list))))
- (ind (cdr (assq item itm-shift)))
- ;; If we're not at an item, there's a child of the item
- ;; point belongs to above. Make sure this slice isn't
- ;; moved within that child by specifying a maximum
- ;; indentation.
- (max-ind (and (not itemp)
- (+ (org-list-get-ind item struct)
- (length (org-list-get-bullet item struct))
- org-list-indent-offset))))
- (push (list down up ind max-ind) sliced-struct)))
+ (delta
+ (if itemp (cdr (assq up itm-shift))
+ ;; If we're not at an item, there's a child of the
+ ;; item point belongs to above. Make sure the less
+ ;; indented line in this slice has the same column
+ ;; as that child.
+ (let* ((child (cdr (assq up acc-end)))
+ (ind (org-list-get-ind child struct))
+ (min-ind most-positive-fixnum))
+ (save-excursion
+ (goto-char up)
+ (while (< (point) down)
+ ;; Ignore empty lines. Also ignore blocks and
+ ;; drawers contents.
+ (unless (looking-at-p "[ \t]*$")
+ (setq min-ind (min (org-get-indentation) min-ind))
+ (cond
+ ((and (looking-at "#\\+BEGIN\\(:\\|_\\S-+\\)")
+ (re-search-forward
+ (format "^[ \t]*#\\+END%s[ \t]*$"
+ (match-string 1))
+ down t)))
+ ((and (looking-at org-drawer-regexp)
+ (re-search-forward "^[ \t]*:END:[ \t]*$"
+ down t)))))
+ (forward-line)))
+ (- ind min-ind)))))
+ (push (list down up delta) sliced-struct)))
;; 3. Shift each slice in buffer, provided delta isn't 0, from
;; end to beginning. Take a special action when beginning is
;; at item bullet.
(dolist (e sliced-struct)
- (unless (and (zerop (nth 2 e)) (not (nth 3 e)))
- (apply shift-body-ind e))
+ (unless (zerop (nth 2 e)) (apply shift-body-ind e))
(let* ((beg (nth 1 e))
(cell (assq beg struct)))
(unless (or (not cell) (equal cell (assq beg old-struct)))
@@ -2060,16 +2095,27 @@ Possible values are: `folded', `children' or `subtree'. See
(defun org-list-item-body-column (item)
"Return column at which body of ITEM should start."
- (let (bpos bcol tpos tcol)
- (save-excursion
- (goto-char item)
- (looking-at "[ \t]*\\(\\S-+\\)\\(.*[ \t]+::\\)?\\([ \t]+\\|$\\)")
- (setq bpos (match-beginning 1) tpos (match-end 0)
- bcol (progn (goto-char bpos) (current-column))
- tcol (progn (goto-char tpos) (current-column)))
- (when (> tcol (+ bcol org-description-max-indent))
- (setq tcol (+ bcol 5))))
- tcol))
+ (save-excursion
+ (goto-char item)
+ (if (save-excursion
+ (end-of-line)
+ (re-search-backward
+ "[ \t]::\\([ \t]\\|$\\)" (line-beginning-position) t))
+ ;; Descriptive list item. Body starts after item's tag, if
+ ;; possible.
+ (let ((start (1+ (- (match-beginning 1) (line-beginning-position))))
+ (ind (org-get-indentation)))
+ (if (> start (+ ind org-list-description-max-indent))
+ (+ ind 5)
+ start))
+ ;; Regular item. Body starts after bullet.
+ (looking-at "[ \t]*\\(\\S-+\\)")
+ (+ (progn (goto-char (match-end 1)) (current-column))
+ (if (and org-list-two-spaces-after-bullet-regexp
+ (string-match-p org-list-two-spaces-after-bullet-regexp
+ (match-string 1)))
+ 2
+ 1)))))
@@ -2210,7 +2256,7 @@ item is invisible."
(unless (or (not itemp)
(save-excursion
(goto-char itemp)
- (outline-invisible-p)))
+ (org-invisible-p)))
(if (save-excursion
(goto-char itemp)
(org-at-item-timer-p))
@@ -2325,9 +2371,6 @@ in subtree, ignoring drawers."
block-item
lim-up
lim-down
- (drawer-re (concat "^[ \t]*:\\("
- (mapconcat #'regexp-quote org-drawers "\\|")
- "\\):[ \t]*$"))
(keyword-re (concat "^[ \t]*\\<\\(" org-scheduled-string
"\\|" org-deadline-string
"\\|" org-closed-string
@@ -2349,7 +2392,8 @@ in subtree, ignoring drawers."
;; time-stamps (scheduled, etc.).
(let ((limit (save-excursion (outline-next-heading) (point))))
(forward-line 1)
- (while (or (looking-at drawer-re) (looking-at keyword-re))
+ (while (or (looking-at org-drawer-regexp)
+ (looking-at keyword-re))
(if (looking-at keyword-re)
(forward-line 1)
(re-search-forward "^[ \t]*:END:" limit nil)))
@@ -2388,7 +2432,7 @@ in subtree, ignoring drawers."
(parents (org-list-parents-alist struct))
(prevs (org-list-prevs-alist struct))
(bottom (copy-marker (org-list-get-bottom-point struct)))
- (items-to-toggle (org-remove-if
+ (items-to-toggle (cl-remove-if
(lambda (e) (or (< e lim-up) (> e lim-down)))
(mapcar #'car struct))))
(mapc (lambda (e) (org-list-set-checkbox
@@ -2439,130 +2483,129 @@ in subtree, ignoring drawers."
(defun org-update-checkbox-count (&optional all)
"Update the checkbox statistics in the current section.
+
This will find all statistic cookies like [57%] and [6/12] and
update them with the current numbers.
With optional prefix argument ALL, do this for the whole buffer."
(interactive "P")
- (save-excursion
- (let ((cookie-re "\\(\\(\\[[0-9]*%\\]\\)\\|\\(\\[[0-9]*/[0-9]*\\]\\)\\)")
- (box-re "^[ \t]*\\([-+*]\\|\\([0-9]+\\|[A-Za-z]\\)[.)]\\)[ \t]+\\(?:\\[@\\(?:start:\\)?\\([0-9]+\\|[A-Za-z]\\)\\][ \t]*\\)?\\(\\[[- X]\\]\\)")
+ (org-with-wide-buffer
+ (let* ((cookie-re "\\(\\(\\[[0-9]*%\\]\\)\\|\\(\\[[0-9]*/[0-9]*\\]\\)\\)")
+ (box-re "^[ \t]*\\([-+*]\\|\\([0-9]+\\|[A-Za-z]\\)[.)]\\)[ \t]+\
+\\(?:\\[@\\(?:start:\\)?\\([0-9]+\\|[A-Za-z]\\)\\][ \t]*\\)?\\(\\[[- X]\\]\\)")
(recursivep
(or (not org-checkbox-hierarchical-statistics)
(string-match "\\"
(or (org-entry-get nil "COOKIE_DATA") ""))))
- (bounds (if all
- (cons (point-min) (point-max))
- (cons (or (ignore-errors (org-back-to-heading t) (point))
- (point-min))
- (save-excursion (outline-next-heading) (point)))))
+ (within-inlinetask (and (not all)
+ (featurep 'org-inlinetask)
+ (org-inlinetask-in-task-p)))
+ (end (cond (all (point-max))
+ (within-inlinetask
+ (save-excursion (outline-next-heading) (point)))
+ (t (save-excursion
+ (org-with-limited-levels (outline-next-heading))
+ (point)))))
(count-boxes
- (function
- ;; Return number of checked boxes and boxes of all types
- ;; in all structures in STRUCTS. If RECURSIVEP is
- ;; non-nil, also count boxes in sub-lists. If ITEM is
- ;; nil, count across the whole structure, else count only
- ;; across subtree whose ancestor is ITEM.
- (lambda (item structs recursivep)
- (let ((c-on 0) (c-all 0))
- (mapc
- (lambda (s)
- (let* ((pre (org-list-prevs-alist s))
- (par (org-list-parents-alist s))
- (items
- (cond
- ((and recursivep item) (org-list-get-subtree item s))
- (recursivep (mapcar #'car s))
- (item (org-list-get-children item s par))
- (t (org-list-get-all-items
- (org-list-get-top-point s) s pre))))
- (cookies (delq nil (mapcar
- (lambda (e)
- (org-list-get-checkbox e s))
- items))))
- (setq c-all (+ (length cookies) c-all)
- c-on (+ (org-count "[X]" cookies) c-on))))
- structs)
- (cons c-on c-all)))))
- (backup-end 1)
- cookies-list structs-bak)
- (goto-char (car bounds))
- ;; 1. Build an alist for each cookie found within BOUNDS. The
- ;; key will be position at beginning of cookie and values
- ;; ending position, format of cookie, and a cell whose car is
- ;; number of checked boxes to report, and cdr total number of
- ;; boxes.
- (while (re-search-forward cookie-re (cdr bounds) t)
- (catch 'skip
- (save-excursion
- (push
- (list
- (match-beginning 1) ; cookie start
- (match-end 1) ; cookie end
- (match-string 2) ; percent?
- (cond ; boxes count
- ;; Cookie is at an heading, but specifically for todo,
- ;; not for checkboxes: skip it.
- ((and (org-at-heading-p)
- (string-match "\\"
- (downcase
- (or (org-entry-get nil "COOKIE_DATA") ""))))
- (throw 'skip nil))
- ;; Cookie is at an heading, but all lists before next
- ;; heading already have been read. Use data collected
- ;; in STRUCTS-BAK. This should only happen when
- ;; heading has more than one cookie on it.
- ((and (org-at-heading-p)
- (<= (save-excursion (outline-next-heading) (point))
- backup-end))
- (funcall count-boxes nil structs-bak recursivep))
- ;; Cookie is at a fresh heading. Grab structure of
- ;; every list containing a checkbox between point and
- ;; next headline, and save them in STRUCTS-BAK.
- ((org-at-heading-p)
- (setq backup-end (save-excursion
- (outline-next-heading) (point))
- structs-bak nil)
- (while (org-list-search-forward box-re backup-end 'move)
- (let* ((struct (org-list-struct))
- (bottom (org-list-get-bottom-point struct)))
- (push struct structs-bak)
- (goto-char bottom)))
- (funcall count-boxes nil structs-bak recursivep))
- ;; Cookie is at an item, and we already have list
- ;; structure stored in STRUCTS-BAK.
- ((and (org-at-item-p)
- (< (point-at-bol) backup-end)
- ;; Only lists in no special context are stored.
- (not (nth 2 (org-list-context))))
- (funcall count-boxes (point-at-bol) structs-bak recursivep))
- ;; Cookie is at an item, but we need to compute list
- ;; structure.
- ((org-at-item-p)
- (let ((struct (org-list-struct)))
- (setq backup-end (org-list-get-bottom-point struct)
- structs-bak (list struct)))
- (funcall count-boxes (point-at-bol) structs-bak recursivep))
- ;; Else, cookie found is at a wrong place. Skip it.
- (t (throw 'skip nil))))
- cookies-list))))
- ;; 2. Apply alist to buffer, in reverse order so positions stay
- ;; unchanged after cookie modifications.
- (mapc (lambda (cookie)
- (let* ((beg (car cookie))
- (end (nth 1 cookie))
- (percentp (nth 2 cookie))
- (checked (car (nth 3 cookie)))
- (total (cdr (nth 3 cookie)))
- (new (if percentp
- (format "[%d%%]" (floor (* 100.0 checked)
- (max 1 total)))
- (format "[%d/%d]" checked total))))
- (goto-char beg)
- (insert new)
- (delete-region (point) (+ (point) (- end beg)))
- (when org-auto-align-tags (org-fix-tags-on-the-fly))))
+ (lambda (item structs recursivep)
+ ;; Return number of checked boxes and boxes of all types
+ ;; in all structures in STRUCTS. If RECURSIVEP is
+ ;; non-nil, also count boxes in sub-lists. If ITEM is
+ ;; nil, count across the whole structure, else count only
+ ;; across subtree whose ancestor is ITEM.
+ (let ((c-on 0) (c-all 0))
+ (dolist (s structs (list c-on c-all))
+ (let* ((pre (org-list-prevs-alist s))
+ (par (org-list-parents-alist s))
+ (items
+ (cond
+ ((and recursivep item) (org-list-get-subtree item s))
+ (recursivep (mapcar #'car s))
+ (item (org-list-get-children item s par))
+ (t (org-list-get-all-items
+ (org-list-get-top-point s) s pre))))
+ (cookies (delq nil (mapcar
+ (lambda (e)
+ (org-list-get-checkbox e s))
+ items))))
+ (cl-incf c-all (length cookies))
+ (cl-incf c-on (cl-count "[X]" cookies :test #'equal)))))))
+ cookies-list cache)
+ ;; Move to start.
+ (cond (all (goto-char (point-min)))
+ (within-inlinetask (org-back-to-heading t))
+ (t (org-with-limited-levels (outline-previous-heading))))
+ ;; Build an alist for each cookie found. The key is the position
+ ;; at beginning of cookie and values ending position, format of
+ ;; cookie, number of checked boxes to report and total number of
+ ;; boxes.
+ (while (re-search-forward cookie-re end t)
+ (let ((context (save-excursion (backward-char)
+ (save-match-data (org-element-context)))))
+ (when (eq (org-element-type context) 'statistics-cookie)
+ (push
+ (append
+ (list (match-beginning 1) (match-end 1) (match-end 2))
+ (let* ((container
+ (org-element-lineage
+ context
+ '(drawer center-block dynamic-block inlinetask item
+ quote-block special-block verse-block)))
+ (beg (if container
+ (org-element-property :contents-begin container)
+ (save-excursion
+ (org-with-limited-levels
+ (outline-previous-heading))
+ (point)))))
+ (or (cdr (assq beg cache))
+ (save-excursion
+ (goto-char beg)
+ (let ((end
+ (if container
+ (org-element-property :contents-end container)
+ (save-excursion
+ (org-with-limited-levels (outline-next-heading))
+ (point))))
+ structs)
+ (while (re-search-forward box-re end t)
+ (let ((element (org-element-at-point)))
+ (when (eq (org-element-type element) 'item)
+ (push (org-element-property :structure element)
+ structs)
+ ;; Skip whole list since we have its
+ ;; structure anyway.
+ (while (setq element (org-element-lineage
+ element '(plain-list)))
+ (goto-char
+ (min (org-element-property :end element)
+ end))))))
+ ;; Cache count for cookies applying to the same
+ ;; area. Then return it.
+ (let ((count
+ (funcall count-boxes
+ (and (eq (org-element-type container)
+ 'item)
+ (org-element-property
+ :begin container))
+ structs
+ recursivep)))
+ (push (cons beg count) cache)
+ count))))))
cookies-list))))
+ ;; Apply alist to buffer.
+ (dolist (cookie cookies-list)
+ (let* ((beg (car cookie))
+ (end (nth 1 cookie))
+ (percent (nth 2 cookie))
+ (checked (nth 3 cookie))
+ (total (nth 4 cookie)))
+ (goto-char beg)
+ (insert
+ (if percent (format "[%d%%]" (floor (* 100.0 checked)
+ (max 1 total)))
+ (format "[%d/%d]" checked total)))
+ (delete-region (point) (+ (point) (- end beg)))
+ (when org-auto-align-tags (org-fix-tags-on-the-fly)))))))
(defun org-get-checkbox-statistics-face ()
"Select the face for checkbox statistics.
@@ -2664,7 +2707,7 @@ Return t if successful."
;; of the subtree mustn't have a child.
(let ((last-item (caar
(reverse
- (org-remove-if
+ (cl-remove-if
(lambda (e) (>= (car e) end))
struct)))))
(org-list-has-child-p last-item struct))))
@@ -2781,7 +2824,7 @@ Return t at each successful move."
((and (= ind (car org-tab-ind-state))
(ignore-errors (org-list-indent-item-generic 1 t struct))))
(t (delete-region (point-at-bol) (point-at-eol))
- (org-indent-to-column (car org-tab-ind-state))
+ (indent-to-column (car org-tab-ind-state))
(insert (cdr org-tab-ind-state) " ")
;; Break cycle
(setq this-command 'identity)))
@@ -2794,7 +2837,8 @@ Return t at each successful move."
(t (user-error "Cannot move item"))))
t))))
-(defun org-sort-list (&optional with-case sorting-type getkey-func compare-func)
+(defun org-sort-list
+ (&optional with-case sorting-type getkey-func compare-func interactive?)
"Sort list items.
The cursor may be at any item of the list that should be sorted.
Sublists are not sorted. Checkboxes, if any, are ignored.
@@ -2820,13 +2864,15 @@ Capital letters will reverse the sort order.
If the SORTING-TYPE is ?f or ?F, then GETKEY-FUNC specifies
a function to be called with point at the beginning of the
-record. It must return either a string or a number that should
-serve as the sorting key for that record. It will then use
-COMPARE-FUNC to compare entries.
+record. It must return a value that is compatible with COMPARE-FUNC,
+the function used to compare entries.
Sorting is done against the visible part of the headlines, it
-ignores hidden links."
- (interactive "P")
+ignores hidden links.
+
+A non-nil value for INTERACTIVE? is used to signal that this
+function is being called interactively."
+ (interactive (list current-prefix-arg nil nil nil t))
(let* ((case-func (if with-case 'identity 'downcase))
(struct (org-list-struct))
(prevs (org-list-prevs-alist struct))
@@ -2838,23 +2884,31 @@ ignores hidden links."
(message
"Sort plain list: [a]lpha [n]umeric [t]ime [f]unc [x]checked A/N/T/F/X means reversed:")
(read-char-exclusive))))
+ (dcst (downcase sorting-type))
(getkey-func
- (or getkey-func
- (and (= (downcase sorting-type) ?f)
- (intern (org-icompleting-read "Sort using function: "
- obarray 'fboundp t nil nil))))))
+ (and (= dcst ?f)
+ (or getkey-func
+ (and interactive?
+ (org-read-function "Function for extracting keys: "))
+ (error "Missing key extractor"))))
+ (sort-func
+ (cond
+ ((= dcst ?a) #'string<)
+ ((= dcst ?f)
+ (or compare-func
+ (and interactive?
+ (org-read-function
+ (concat "Function for comparing keys "
+ "(empty for default `sort-subr' predicate): ")
+ 'allow-empty))))
+ ((= dcst ?t) #'<)
+ ((= dcst ?x) #'string<))))
(message "Sorting items...")
(save-restriction
(narrow-to-region start end)
(goto-char (point-min))
- (let* ((dcst (downcase sorting-type))
- (case-fold-search nil)
+ (let* ((case-fold-search nil)
(now (current-time))
- (sort-func (cond
- ((= dcst ?a) 'string<)
- ((= dcst ?f) compare-func)
- ((= dcst ?t) '<)
- ((= dcst ?x) 'string<)))
(next-record (lambda ()
(skip-chars-forward " \r\t\n")
(or (eobp) (beginning-of-line))))
@@ -2908,134 +2962,255 @@ ignores hidden links."
(run-hooks 'org-after-sorting-entries-or-items-hook)
(message "Sorting items...done")))))
+(defun org-toggle-item (arg)
+ "Convert headings or normal lines to items, items to normal lines.
+If there is no active region, only the current line is considered.
+
+If the first non blank line in the region is a headline, convert
+all headlines to items, shifting text accordingly.
+
+If it is an item, convert all items to normal lines.
+
+If it is normal text, change region into a list of items.
+With a prefix argument ARG, change the region in a single item."
+ (interactive "P")
+ (let ((shift-text
+ (lambda (ind end)
+ ;; Shift text in current section to IND, from point to END.
+ ;; The function leaves point to END line.
+ (let ((min-i 1000) (end (copy-marker end)))
+ ;; First determine the minimum indentation (MIN-I) of
+ ;; the text.
+ (save-excursion
+ (catch 'exit
+ (while (< (point) end)
+ (let ((i (org-get-indentation)))
+ (cond
+ ;; Skip blank lines and inline tasks.
+ ((looking-at "^[ \t]*$"))
+ ((looking-at org-outline-regexp-bol))
+ ;; We can't find less than 0 indentation.
+ ((zerop i) (throw 'exit (setq min-i 0)))
+ ((< i min-i) (setq min-i i))))
+ (forward-line))))
+ ;; Then indent each line so that a line indented to
+ ;; MIN-I becomes indented to IND. Ignore blank lines
+ ;; and inline tasks in the process.
+ (let ((delta (- ind min-i)))
+ (while (< (point) end)
+ (unless (or (looking-at "^[ \t]*$")
+ (looking-at org-outline-regexp-bol))
+ (indent-line-to (+ (org-get-indentation) delta)))
+ (forward-line))))))
+ (skip-blanks
+ (lambda (pos)
+ ;; Return beginning of first non-blank line, starting from
+ ;; line at POS.
+ (save-excursion
+ (goto-char pos)
+ (skip-chars-forward " \r\t\n")
+ (point-at-bol))))
+ beg end)
+ ;; Determine boundaries of changes.
+ (if (org-region-active-p)
+ (setq beg (funcall skip-blanks (region-beginning))
+ end (copy-marker (region-end)))
+ (setq beg (funcall skip-blanks (point-at-bol))
+ end (copy-marker (point-at-eol))))
+ ;; Depending on the starting line, choose an action on the text
+ ;; between BEG and END.
+ (org-with-limited-levels
+ (save-excursion
+ (goto-char beg)
+ (cond
+ ;; Case 1. Start at an item: de-itemize. Note that it only
+ ;; happens when a region is active: `org-ctrl-c-minus'
+ ;; would call `org-cycle-list-bullet' otherwise.
+ ((org-at-item-p)
+ (while (< (point) end)
+ (when (org-at-item-p)
+ (skip-chars-forward " \t")
+ (delete-region (point) (match-end 0)))
+ (forward-line)))
+ ;; Case 2. Start at an heading: convert to items.
+ ((org-at-heading-p)
+ (let* ((bul (org-list-bullet-string "-"))
+ (bul-len (length bul))
+ ;; Indentation of the first heading. It should be
+ ;; relative to the indentation of its parent, if any.
+ (start-ind (save-excursion
+ (cond
+ ((not org-adapt-indentation) 0)
+ ((not (outline-previous-heading)) 0)
+ (t (length (match-string 0))))))
+ ;; Level of first heading. Further headings will be
+ ;; compared to it to determine hierarchy in the list.
+ (ref-level (org-reduced-level (org-outline-level))))
+ (while (< (point) end)
+ (let* ((level (org-reduced-level (org-outline-level)))
+ (delta (max 0 (- level ref-level)))
+ (todo-state (org-get-todo-state)))
+ ;; If current headline is less indented than the first
+ ;; one, set it as reference, in order to preserve
+ ;; subtrees.
+ (when (< level ref-level) (setq ref-level level))
+ ;; Remove stars and TODO keyword.
+ (let ((case-fold-search nil)) (looking-at org-todo-line-regexp))
+ (delete-region (point) (or (match-beginning 3)
+ (line-end-position)))
+ (insert bul)
+ (indent-line-to (+ start-ind (* delta bul-len)))
+ ;; Turn TODO keyword into a check box.
+ (when todo-state
+ (let* ((struct (org-list-struct))
+ (old (copy-tree struct)))
+ (org-list-set-checkbox
+ (line-beginning-position)
+ struct
+ (if (member todo-state org-done-keywords)
+ "[X]"
+ "[ ]"))
+ (org-list-write-struct struct
+ (org-list-parents-alist struct)
+ old)))
+ ;; Ensure all text down to END (or SECTION-END) belongs
+ ;; to the newly created item.
+ (let ((section-end (save-excursion
+ (or (outline-next-heading) (point)))))
+ (forward-line)
+ (funcall shift-text
+ (+ start-ind (* (1+ delta) bul-len))
+ (min end section-end)))))))
+ ;; Case 3. Normal line with ARG: make the first line of region
+ ;; an item, and shift indentation of others lines to
+ ;; set them as item's body.
+ (arg (let* ((bul (org-list-bullet-string "-"))
+ (bul-len (length bul))
+ (ref-ind (org-get-indentation)))
+ (skip-chars-forward " \t")
+ (insert bul)
+ (forward-line)
+ (while (< (point) end)
+ ;; Ensure that lines less indented than first one
+ ;; still get included in item body.
+ (funcall shift-text
+ (+ ref-ind bul-len)
+ (min end (save-excursion (or (outline-next-heading)
+ (point)))))
+ (forward-line))))
+ ;; Case 4. Normal line without ARG: turn each non-item line
+ ;; into an item.
+ (t
+ (while (< (point) end)
+ (unless (or (org-at-heading-p) (org-at-item-p))
+ (when (looking-at "\\([ \t]*\\)\\(\\S-\\)")
+ (replace-match
+ (concat "\\1" (org-list-bullet-string "-") "\\2"))))
+ (forward-line))))))))
;;; Send and receive lists
-(defun org-list-parse-list (&optional delete)
+(defun org-list-to-lisp (&optional delete)
"Parse the list at point and maybe DELETE it.
Return a list whose car is a symbol of list type, among
`ordered', `unordered' and `descriptive'. Then, each item is
-a list whose car is counter, and cdr are strings and other
-sub-lists. Inside strings, check-boxes are replaced by
-\"[CBON]\", \"[CBOFF]\" and \"[CBTRANS]\".
+a list of strings and other sub-lists.
For example, the following list:
-1. first item
- + sub-item one
- + [X] sub-item two
- more text in first item
-2. [@3] last item
+ 1. first item
+ + sub-item one
+ + [X] sub-item two
+ more text in first item
+ 2. [@3] last item
-will be parsed as:
+is parsed as
(ordered
- (nil \"first item\"
- (unordered
- (nil \"sub-item one\")
- (nil \"[CBON] sub-item two\"))
- \"more text in first item\")
- (3 \"last item\"))
-
-Point is left at list end."
- (defvar parse-item) ;FIXME: Or use `cl-labels' or `letrec'.
- (let* ((struct (org-list-struct))
- (prevs (org-list-prevs-alist struct))
- (parents (org-list-parents-alist struct))
- (top (org-list-get-top-point struct))
- (bottom (org-list-get-bottom-point struct))
- out
- (get-text
- (function
- ;; Return text between BEG and END, trimmed, with
- ;; checkboxes replaced.
- (lambda (beg end)
- (let ((text (org-trim (buffer-substring beg end))))
- (if (string-match "\\`\\[\\([-X ]\\)\\]" text)
- (replace-match
- (let ((box (match-string 1 text)))
- (cond
- ((equal box " ") "CBOFF")
- ((equal box "-") "CBTRANS")
- (t "CBON")))
- t nil text 1)
- text)))))
- (parse-sublist
- (function
- ;; Return a list whose car is list type and cdr a list of
- ;; items' body.
- (lambda (e)
- (cons (org-list-get-list-type (car e) struct prevs)
- (mapcar parse-item e)))))
- (parse-item
- (function
- ;; Return a list containing counter of item, if any, text
- ;; and any sublist inside it.
- (lambda (e)
- (let ((start (save-excursion
- (goto-char e)
- (looking-at "[ \t]*\\S-+\\([ \t]+\\[@\\(start:\\)?\\([0-9]+\\|[a-zA-Z]\\)\\]\\)?[ \t]*")
- (match-end 0)))
- ;; Get counter number. For alphabetic counter, get
- ;; its position in the alphabet.
- (counter (let ((c (org-list-get-counter e struct)))
- (cond
- ((not c) nil)
- ((string-match "[A-Za-z]" c)
- (- (string-to-char (upcase (match-string 0 c)))
- 64))
- ((string-match "[0-9]+" c)
- (string-to-number (match-string 0 c))))))
- (childp (org-list-has-child-p e struct))
- (end (org-list-get-item-end e struct)))
- ;; If item has a child, store text between bullet and
- ;; next child, then recursively parse all sublists. At
- ;; the end of each sublist, check for the presence of
- ;; text belonging to the original item.
- (if childp
- (let* ((children (org-list-get-children e struct parents))
- (body (list (funcall get-text start childp))))
- (while children
- (let* ((first (car children))
- (sub (org-list-get-all-items first struct prevs))
- (last-c (car (last sub)))
- (last-end (org-list-get-item-end last-c struct)))
- (push (funcall parse-sublist sub) body)
- ;; Remove children from the list just parsed.
- (setq children (cdr (member last-c children)))
- ;; There is a chunk of text belonging to the
- ;; item if last child doesn't end where next
- ;; child starts or where item ends.
- (unless (= (or (car children) end) last-end)
- (push (funcall get-text
- last-end (or (car children) end))
- body))))
- (cons counter (nreverse body)))
- (list counter (funcall get-text start end))))))))
+ (\"first item\"
+ (unordered
+ (\"sub-item one\")
+ (\"[X] sub-item two\"))
+ \"more text in first item\")
+ (\"[@3] last item\"))
+
+Point is left at list's end."
+ (letrec ((struct (org-list-struct))
+ (prevs (org-list-prevs-alist struct))
+ (parents (org-list-parents-alist struct))
+ (top (org-list-get-top-point struct))
+ (bottom (org-list-get-bottom-point struct))
+ (trim
+ (lambda (text)
+ ;; Remove indentation and final newline from TEXT.
+ (org-remove-indentation
+ (if (string-match-p "\n\\'" text)
+ (substring text 0 -1)
+ text))))
+ (parse-sublist
+ (lambda (e)
+ ;; Return a list whose car is list type and cdr a list
+ ;; of items' body.
+ (cons (org-list-get-list-type (car e) struct prevs)
+ (mapcar parse-item e))))
+ (parse-item
+ (lambda (e)
+ ;; Return a list containing counter of item, if any,
+ ;; text and any sublist inside it.
+ (let* ((end (org-list-get-item-end e struct))
+ (children (org-list-get-children e struct parents))
+ (body
+ (save-excursion
+ (goto-char e)
+ (looking-at "[ \t]*\\S-+[ \t]*")
+ (list
+ (funcall
+ trim
+ (concat
+ (make-string (string-width (match-string 0)) ?\s)
+ (buffer-substring-no-properties
+ (match-end 0) (or (car children) end))))))))
+ (while children
+ (let* ((child (car children))
+ (sub (org-list-get-all-items child struct prevs))
+ (last-in-sub (car (last sub))))
+ (push (funcall parse-sublist sub) body)
+ ;; Remove whole sub-list from children.
+ (setq children (cdr (memq last-in-sub children)))
+ ;; There is a chunk of text belonging to the item
+ ;; if last child doesn't end where next child
+ ;; starts or where item ends.
+ (let ((sub-end (org-list-get-item-end last-in-sub struct))
+ (next (or (car children) end)))
+ (when (/= sub-end next)
+ (push (funcall
+ trim
+ (buffer-substring-no-properties sub-end next))
+ body)))))
+ (nreverse body)))))
;; Store output, take care of cursor position and deletion of
;; list, then return output.
- (setq out (funcall parse-sublist (org-list-get-all-items top struct prevs)))
- (goto-char top)
- (when delete
- (delete-region top bottom)
- (when (and (not (looking-at "[ \t]*$")) (looking-at org-list-end-re))
- (replace-match "")))
- out))
+ (prog1 (funcall parse-sublist (org-list-get-all-items top struct prevs))
+ (goto-char top)
+ (when delete
+ (delete-region top bottom)
+ (when (and (not (looking-at "[ \t]*$")) (looking-at org-list-end-re))
+ (replace-match ""))))))
(defun org-list-make-subtree ()
"Convert the plain list at point into a subtree."
(interactive)
(if (not (ignore-errors (goto-char (org-in-item-p))))
(error "Not in a list")
- (let ((list (save-excursion (org-list-parse-list t))))
+ (let ((list (save-excursion (org-list-to-lisp t))))
(insert (org-list-to-subtree list)))))
(defun org-list-insert-radio-list ()
"Insert a radio list template appropriate for this major mode."
(interactive)
- (let* ((e (assq major-mode org-list-radio-list-templates))
+ (let* ((e (cl-assoc-if #'derived-mode-p org-list-radio-list-templates))
(txt (nth 1 e))
name pos)
(unless e (error "No radio list setup defined for %s" major-mode))
@@ -3055,11 +3230,13 @@ for this list."
(catch 'exit
(unless (org-at-item-p) (error "Not at a list item"))
(save-excursion
- (re-search-backward "#\\+ORGLST" nil t)
- (unless (looking-at "#\\+ORGLST:[ \t]+SEND[ \t]+\\(\\S-+\\)[ \t]+\\(\\S-+\\)")
- (if maybe (throw 'exit nil)
- (error "Don't know how to transform this list"))))
- (let* ((name (match-string 1))
+ (let ((case-fold-search t))
+ (re-search-backward "^[ \t]*#\\+ORGLST:" nil t)
+ (unless (looking-at
+ "[ \t]*#\\+ORGLST:[ \t]+SEND[ \t]+\\(\\S-+\\)[ \t]+\\([^ \t\n]+\\)")
+ (if maybe (throw 'exit nil)
+ (error "Don't know how to transform this list")))))
+ (let* ((name (regexp-quote (match-string 1)))
(transform (intern (match-string 2)))
(bottom-point
(save-excursion
@@ -3071,220 +3248,342 @@ for this list."
(re-search-backward "#\\+ORGLST" nil t)
(re-search-forward (org-item-beginning-re) bottom-point t)
(match-beginning 0)))
- (plain-list (buffer-substring-no-properties top-point bottom-point))
- beg)
+ (plain-list (save-excursion
+ (goto-char top-point)
+ (org-list-to-lisp))))
(unless (fboundp transform)
(error "No such transformation function %s" transform))
(let ((txt (funcall transform plain-list)))
- ;; Find the insertion place
+ ;; Find the insertion(s) place(s).
(save-excursion
(goto-char (point-min))
- (unless (re-search-forward
- (concat "BEGIN RECEIVE ORGLST +"
- name
- "\\([ \t]\\|$\\)")
- nil t)
- (error "Don't know where to insert translated list"))
- (goto-char (match-beginning 0))
- (beginning-of-line 2)
- (setq beg (point))
- (unless (re-search-forward (concat "END RECEIVE ORGLST +" name) nil t)
- (error "Cannot find end of insertion region"))
- (delete-region beg (point-at-bol))
- (goto-char beg)
- (insert txt "\n")))
- (message "List converted and installed at receiver location"))))
-
-(defsubst org-list-item-trim-br (item)
- "Trim line breaks in a list ITEM."
- (setq item (replace-regexp-in-string "\n +" " " item)))
+ (let ((receiver-count 0)
+ (begin-re (format "BEGIN +RECEIVE +ORGLST +%s\\([ \t]\\|$\\)"
+ name))
+ (end-re (format "END +RECEIVE +ORGLST +%s\\([ \t]\\|$\\)"
+ name)))
+ (while (re-search-forward begin-re nil t)
+ (cl-incf receiver-count)
+ (let ((beg (line-beginning-position 2)))
+ (unless (re-search-forward end-re nil t)
+ (user-error "Cannot find end of receiver location at %d" beg))
+ (beginning-of-line)
+ (delete-region beg (point))
+ (insert txt "\n")))
+ (cond
+ ((> receiver-count 1)
+ (message "List converted and installed at receiver locations"))
+ ((= receiver-count 1)
+ (message "List converted and installed at receiver location"))
+ (t (user-error "No valid receiver location found")))))))))
(defun org-list-to-generic (list params)
- "Convert a LIST parsed through `org-list-parse-list' to other formats.
-Valid parameters PARAMS are:
-
-:ustart String to start an unordered list
-:uend String to end an unordered list
-
-:ostart String to start an ordered list
-:oend String to end an ordered list
-
-:dstart String to start a descriptive list
-:dend String to end a descriptive list
-:dtstart String to start a descriptive term
-:dtend String to end a descriptive term
-:ddstart String to start a description
-:ddend String to end a description
-
-:splice When set to t, return only list body lines, don't wrap
- them into :[u/o]start and :[u/o]end. Default is nil.
-
-:istart String to start a list item.
-:icount String to start an item with a counter.
-:iend String to end a list item
-:isep String to separate items
-:lsep String to separate sublists
-:csep String to separate text from a sub-list
-
-:cboff String to insert for an unchecked check-box
-:cbon String to insert for a checked check-box
-:cbtrans String to insert for a check-box in transitional state
-
-:nobr Non-nil means remove line breaks in lists items.
-
-Alternatively, each parameter can also be a form returning
-a string. These sexp can use keywords `counter' and `depth',
-representing respectively counter associated to the current
-item, and depth of the current sub-list, starting at 0.
-Obviously, `counter' is only available for parameters applying to
-items."
- (interactive)
- (let* ((p params)
- (splicep (plist-get p :splice))
- (ostart (plist-get p :ostart))
- (oend (plist-get p :oend))
- (ustart (plist-get p :ustart))
- (uend (plist-get p :uend))
- (dstart (plist-get p :dstart))
- (dend (plist-get p :dend))
- (dtstart (plist-get p :dtstart))
- (dtend (plist-get p :dtend))
- (ddstart (plist-get p :ddstart))
- (ddend (plist-get p :ddend))
- (istart (plist-get p :istart))
- (icount (plist-get p :icount))
- (iend (plist-get p :iend))
- (isep (plist-get p :isep))
- (lsep (plist-get p :lsep))
- (csep (plist-get p :csep))
- (cbon (plist-get p :cbon))
- (cboff (plist-get p :cboff))
- (cbtrans (plist-get p :cbtrans))
- (nobr (plist-get p :nobr))
- export-sublist ; for byte-compiler
- (export-item
- (function
- ;; Export an item ITEM of type TYPE, at DEPTH. First
- ;; string in item is treated in a special way as it can
- ;; bring extra information that needs to be processed.
- (lambda (item type depth)
- (let* ((counter (pop item))
- (fmt (concat
- (cond
- ((eq type 'descriptive)
- ;; Stick DTSTART to ISTART by
- ;; left-trimming the latter.
- (concat (let ((s (eval istart)))
- (or (and (string-match "[ \t\n\r]+\\'" s)
- (replace-match "" t t s))
- istart))
- "%s" (eval ddend)))
- ((and counter (eq type 'ordered))
- (concat (eval icount) "%s"))
- (t (concat (eval istart) "%s")))
- (eval iend)))
- (first (car item)))
- ;; Replace checkbox if any is found.
- (cond
- ((string-match "\\[CBON\\]" first)
- (setq first (replace-match cbon t t first)))
- ((string-match "\\[CBOFF\\]" first)
- (setq first (replace-match cboff t t first)))
- ((string-match "\\[CBTRANS\\]" first)
- (setq first (replace-match cbtrans t t first))))
- ;; Replace line breaks if required
- (when nobr (setq first (org-list-item-trim-br first)))
- ;; Insert descriptive term if TYPE is `descriptive'.
- (when (eq type 'descriptive)
- (let* ((complete (string-match "^\\(.*\\)[ \t]+::" first))
- (term (if complete
- (save-match-data
- (org-trim (match-string 1 first)))
- "???"))
- (desc (if complete
- (org-trim (substring first (match-end 0)))
- first)))
- (setq first (concat (eval dtstart) term (eval dtend)
- (eval ddstart) desc))))
- (setcar item first)
- (format fmt
- (mapconcat (lambda (e)
- (if (stringp e) e
- (funcall export-sublist e (1+ depth))))
- item (or (eval csep) "")))))))
- (export-sublist
- (function
- ;; Export sublist SUB at DEPTH.
- (lambda (sub depth)
- (let* ((type (car sub))
- (items (cdr sub))
- (fmt (concat (cond
- (splicep "%s")
- ((eq type 'ordered)
- (concat (eval ostart) "%s" (eval oend)))
- ((eq type 'descriptive)
- (concat (eval dstart) "%s" (eval dend)))
- (t (concat (eval ustart) "%s" (eval uend))))
- (eval lsep))))
- (format fmt (mapconcat (lambda (e)
- (funcall export-item e type depth))
- items (or (eval isep) ""))))))))
- (concat (funcall export-sublist list 0) "\n")))
-
-(defun org-list-to-latex (list &optional _params)
+ "Convert a LIST parsed through `org-list-to-lisp' to a custom format.
+
+LIST is a list as returned by `org-list-to-lisp', which see.
+PARAMS is a property list of parameters used to tweak the output
+format.
+
+Valid parameters are:
+
+:backend, :raw
+
+ Export back-end used as a basis to transcode elements of the
+ list, when no specific parameter applies to it. It is also
+ used to translate its contents. You can prevent this by
+ setting :raw property to a non-nil value.
+
+:splice
+
+ When non-nil, only export the contents of the top most plain
+ list, effectively ignoring its opening and closing lines.
+
+:ustart, :uend
+
+ Strings to start and end an unordered list. They can also be
+ set to a function returning a string or nil, which will be
+ called with the depth of the list, counting from 1.
+
+:ostart, :oend
+
+ Strings to start and end an ordered list. They can also be set
+ to a function returning a string or nil, which will be called
+ with the depth of the list, counting from 1.
+
+:dstart, :dend
+
+ Strings to start and end a descriptive list. They can also be
+ set to a function returning a string or nil, which will be
+ called with the depth of the list, counting from 1.
+
+:dtstart, :dtend, :ddstart, :ddend
+
+ Strings to start and end a descriptive term.
+
+:istart, :iend
+
+ Strings to start or end a list item, and to start a list item
+ with a counter. They can also be set to a function returning
+ a string or nil, which will be called with the depth of the
+ item, counting from 1.
+
+:icount
+
+ Strings to start a list item with a counter. It can also be
+ set to a function returning a string or nil, which will be
+ called with two arguments: the depth of the item, counting from
+ 1, and the counter. Its value, when non-nil, has precedence
+ over `:istart'.
+
+:isep
+
+ String used to separate items. It can also be set to
+ a function returning a string or nil, which will be called with
+ the depth of the items, counting from 1. It always start on
+ a new line.
+
+:cbon, :cboff, :cbtrans
+
+ String to insert, respectively, an un-checked check-box,
+ a checked check-box and a check-box in transitional state."
+ (require 'ox)
+ (let* ((backend (plist-get params :backend))
+ (custom-backend
+ (org-export-create-backend
+ :parent (or backend 'org)
+ :transcoders
+ `((plain-list . ,(org-list--to-generic-plain-list params))
+ (item . ,(org-list--to-generic-item params))
+ (macro . (lambda (m c i) (org-element-macro-interpreter m nil))))))
+ data info)
+ ;; Write LIST back into Org syntax and parse it.
+ (with-temp-buffer
+ (let ((org-inhibit-startup t)) (org-mode))
+ (letrec ((insert-list
+ (lambda (l)
+ (dolist (i (cdr l))
+ (funcall insert-item i (car l)))))
+ (insert-item
+ (lambda (i type)
+ (let ((start (point)))
+ (insert (if (eq type 'ordered) "1. " "- "))
+ (dolist (e i)
+ (if (consp e) (funcall insert-list e)
+ (insert e)
+ (insert "\n")))
+ (beginning-of-line)
+ (save-excursion
+ (let ((ind (if (eq type 'ordered) 3 2)))
+ (while (> (point) start)
+ (unless (looking-at-p "[ \t]*$")
+ (indent-to ind))
+ (forward-line -1))))))))
+ (funcall insert-list list))
+ (setf data
+ (org-element-map (org-element-parse-buffer) 'plain-list
+ #'identity nil t))
+ (setf info (org-export-get-environment backend nil params)))
+ (when (and backend (symbolp backend) (not (org-export-get-backend backend)))
+ (user-error "Unknown :backend value"))
+ (unless backend (require 'ox-org))
+ ;; When`:raw' property has a non-nil value, turn all objects back
+ ;; into Org syntax.
+ (when (and backend (plist-get params :raw))
+ (org-element-map data org-element-all-objects
+ (lambda (object)
+ (org-element-set-element
+ object (org-element-interpret-data object)))))
+ ;; We use a low-level mechanism to export DATA so as to skip all
+ ;; usual pre-processing and post-processing, i.e., hooks, filters,
+ ;; Babel code evaluation, include keywords and macro expansion,
+ ;; and filters.
+ (let ((output (org-export-data-with-backend data custom-backend info)))
+ ;; Remove final newline.
+ (if (org-string-nw-p output) (substring-no-properties output 0 -1) ""))))
+
+(defun org-list--depth (element)
+ "Return the level of ELEMENT within current plain list.
+ELEMENT is either an item or a plain list."
+ (cl-count-if (lambda (ancestor) (eq (org-element-type ancestor) 'plain-list))
+ (org-element-lineage element nil t)))
+
+(defun org-list--trailing-newlines (string)
+ "Return the number of trailing newlines in STRING."
+ (with-temp-buffer
+ (insert string)
+ (skip-chars-backward " \t\n")
+ (count-lines (line-beginning-position 2) (point-max))))
+
+(defun org-list--generic-eval (value &rest args)
+ "Evaluate VALUE according to its type.
+VALUE is either nil, a string or a function. In the latter case,
+it is called with arguments ARGS."
+ (cond ((null value) nil)
+ ((stringp value) value)
+ ((functionp value) (apply value args))
+ (t (error "Wrong value: %s" value))))
+
+(defun org-list--to-generic-plain-list (params)
+ "Return a transcoder for `plain-list' elements.
+PARAMS is a plist used to tweak the behavior of the transcoder."
+ (let ((ustart (plist-get params :ustart))
+ (uend (plist-get params :uend))
+ (ostart (plist-get params :ostart))
+ (oend (plist-get params :oend))
+ (dstart (plist-get params :dstart))
+ (dend (plist-get params :dend))
+ (splice (plist-get params :splice))
+ (backend (plist-get params :backend)))
+ (lambda (plain-list contents info)
+ (let* ((type (org-element-property :type plain-list))
+ (depth (org-list--depth plain-list))
+ (start (and (not splice)
+ (org-list--generic-eval
+ (pcase type
+ (`ordered ostart)
+ (`unordered ustart)
+ (_ dstart))
+ depth)))
+ (end (and (not splice)
+ (org-list--generic-eval
+ (pcase type
+ (`ordered oend)
+ (`unordered uend)
+ (_ dend))
+ depth))))
+ ;; Make sure trailing newlines in END appear in the output by
+ ;; setting `:post-blank' property to their number.
+ (when end
+ (org-element-put-property
+ plain-list :post-blank (org-list--trailing-newlines end)))
+ ;; Build output.
+ (concat (and start (concat start "\n"))
+ (if (or start end splice (not backend))
+ contents
+ (org-export-with-backend backend plain-list contents info))
+ end)))))
+
+(defun org-list--to-generic-item (params)
+ "Return a transcoder for `item' elements.
+PARAMS is a plist used to tweak the behavior of the transcoder."
+ (let ((backend (plist-get params :backend))
+ (istart (plist-get params :istart))
+ (iend (plist-get params :iend))
+ (isep (plist-get params :isep))
+ (icount (plist-get params :icount))
+ (cboff (plist-get params :cboff))
+ (cbon (plist-get params :cbon))
+ (cbtrans (plist-get params :cbtrans))
+ (dtstart (plist-get params :dtstart))
+ (dtend (plist-get params :dtend))
+ (ddstart (plist-get params :ddstart))
+ (ddend (plist-get params :ddend)))
+ (lambda (item contents info)
+ (let* ((type
+ (org-element-property :type (org-element-property :parent item)))
+ (tag (org-element-property :tag item))
+ (depth (org-list--depth item))
+ (separator (and (org-export-get-next-element item info)
+ (org-list--generic-eval isep depth)))
+ (closing (pcase (org-list--generic-eval iend depth)
+ ((or `nil `"") "\n")
+ ((and (guard separator) s)
+ (if (equal (substring s -1) "\n") s (concat s "\n")))
+ (s s))))
+ ;; When a closing line or a separator is provided, make sure
+ ;; its trailing newlines are taken into account when building
+ ;; output. This is done by setting `:post-blank' property to
+ ;; the number of such lines in the last line to be added.
+ (let ((last-string (or separator closing)))
+ (when last-string
+ (org-element-put-property
+ item
+ :post-blank
+ (max (1- (org-list--trailing-newlines last-string)) 0))))
+ ;; Build output.
+ (concat
+ (let ((c (org-element-property :counter item)))
+ (if c (org-list--generic-eval icount depth c)
+ (org-list--generic-eval istart depth)))
+ (let ((body
+ (if (or istart iend icount cbon cboff cbtrans (not backend)
+ (and (eq type 'descriptive)
+ (or dtstart dtend ddstart ddend)))
+ (concat
+ (pcase (org-element-property :checkbox item)
+ (`on cbon)
+ (`off cboff)
+ (`trans cbtrans))
+ (and tag
+ (concat dtstart
+ (if backend
+ (org-export-data-with-backend
+ tag backend info)
+ (org-element-interpret-data tag))
+ dtend))
+ (and tag ddstart)
+ (if (= (length contents) 0) "" (substring contents 0 -1))
+ (and tag ddend))
+ (org-export-with-backend backend item contents info))))
+ ;; Remove final newline.
+ (if (equal body "") ""
+ (substring (org-element-normalize-string body) 0 -1)))
+ closing
+ separator)))))
+
+(defun org-list-to-latex (list &optional params)
"Convert LIST into a LaTeX list.
-LIST is as string representing the list to transform, as Org
-syntax. Return converted list as a string."
+LIST is a parsed plain list, as returned by `org-list-to-lisp'.
+PARAMS is a property list with overruling parameters for
+`org-list-to-generic'. Return converted list as a string."
(require 'ox-latex)
- (org-export-string-as list 'latex t))
+ (org-list-to-generic list (org-combine-plists '(:backend latex) params)))
-(defun org-list-to-html (list)
+(defun org-list-to-html (list &optional params)
"Convert LIST into a HTML list.
-LIST is as string representing the list to transform, as Org
-syntax. Return converted list as a string."
+LIST is a parsed plain list, as returned by `org-list-to-lisp'.
+PARAMS is a property list with overruling parameters for
+`org-list-to-generic'. Return converted list as a string."
(require 'ox-html)
- (org-export-string-as list 'html t))
+ (org-list-to-generic list (org-combine-plists '(:backend html) params)))
-(defun org-list-to-texinfo (list &optional _params)
+(defun org-list-to-texinfo (list &optional params)
"Convert LIST into a Texinfo list.
-LIST is as string representing the list to transform, as Org
-syntax. Return converted list as a string."
+LIST is a parsed plain list, as returned by `org-list-to-lisp'.
+PARAMS is a property list with overruling parameters for
+`org-list-to-generic'. Return converted list as a string."
(require 'ox-texinfo)
- (org-export-string-as list 'texinfo t))
+ (org-list-to-generic list (org-combine-plists '(:backend texinfo) params)))
(defun org-list-to-subtree (list &optional params)
"Convert LIST into an Org subtree.
-LIST is as returned by `org-list-parse-list'. PARAMS is a property list
-with overruling parameters for `org-list-to-generic'."
- (defvar get-stars) (defvar org--blankp)
- (let* ((rule (cdr (assq 'heading org-blank-before-new-entry)))
+LIST is as returned by `org-list-to-lisp'. PARAMS is a property
+list with overruling parameters for `org-list-to-generic'."
+ (let* ((blank (pcase (cdr (assq 'heading org-blank-before-new-entry))
+ (`t t)
+ (`auto (save-excursion
+ (org-with-limited-levels (outline-previous-heading))
+ (org-previous-line-empty-p)))))
(level (org-reduced-level (or (org-current-level) 0)))
- (org--blankp (or (eq rule t)
- (and (eq rule 'auto)
- (save-excursion
- (outline-previous-heading)
- (org-previous-line-empty-p)))))
- (get-stars ;FIXME: Can't rename without renaming it in org.el as well!
- (function
- ;; Return the string for the heading, depending on depth D
- ;; of current sub-list.
- (lambda (d)
- (let ((oddeven-level (+ level d 1)))
- (concat (make-string (if org-odd-levels-only
- (1- (* 2 oddeven-level))
- oddeven-level)
- ?*)
- " "))))))
+ (make-stars
+ (lambda (depth)
+ ;; Return the string for the heading, depending on DEPTH
+ ;; of current sub-list.
+ (let ((oddeven-level (+ level depth)))
+ (concat (make-string (if org-odd-levels-only
+ (1- (* 2 oddeven-level))
+ oddeven-level)
+ ?*)
+ " ")))))
(org-list-to-generic
list
(org-combine-plists
- '(:splice t
- :dtstart " " :dtend " "
- :istart (funcall get-stars depth)
- :icount (funcall get-stars depth)
- :isep (if org--blankp "\n\n" "\n")
- :csep (if org--blankp "\n\n" "\n")
- :cbon "DONE" :cboff "TODO" :cbtrans "TODO")
+ (list :splice t
+ :istart make-stars
+ :icount make-stars
+ :dtstart " " :dtend " "
+ :isep (if blank "\n\n" "\n")
+ :cbon "DONE " :cboff "TODO " :cbtrans "TODO ")
params))))
(provide 'org-list)
diff --git a/lisp/org/org-macro.el b/lisp/org/org-macro.el
index f4919d1385e..3dc9c5450ed 100644
--- a/lisp/org/org-macro.el
+++ b/lisp/org/org-macro.el
@@ -1,4 +1,4 @@
-;;; org-macro.el --- Macro Replacement Code for Org Mode
+;;; org-macro.el --- Macro Replacement Code for Org -*- lexical-binding: t; -*-
;; Copyright (C) 2013-2017 Free Software Foundation, Inc.
@@ -30,6 +30,10 @@
;; `org-macro-initialize-templates', which recursively calls
;; `org-macro--collect-macros' in order to read setup files.
+;; Argument in macros are separated with commas. Proper escaping rules
+;; are implemented in `org-macro-escape-arguments' and arguments can
+;; be extracted from a string with `org-macro-extract-arguments'.
+
;; Along with macros defined through #+MACRO: keyword, default
;; templates include the following hard-coded macros:
;; {{{time(format-string)}}}, {{{property(node-property)}}},
@@ -39,19 +43,25 @@
;; {{{email}}} and {{{title}}} macros.
;;; Code:
+(require 'cl-lib)
(require 'org-macs)
+(require 'org-compat)
-(declare-function org-element-at-point "org-element" (&optional keep-trail))
+(declare-function org-element-at-point "org-element" ())
(declare-function org-element-context "org-element" (&optional element))
+(declare-function org-element-macro-parser "org-element" ())
(declare-function org-element-property "org-element" (property element))
(declare-function org-element-type "org-element" (element))
-(declare-function org-remove-double-quotes "org" (s))
-(declare-function org-mode "org" ())
(declare-function org-file-contents "org" (file &optional noerror))
+(declare-function org-in-commented-heading-p "org" (&optional no-inheritance))
+(declare-function org-mode "org" ())
+(declare-function vc-backend "vc-hooks" (f))
+(declare-function vc-call "vc-hooks" (fun file &rest args) t)
+(declare-function vc-exec-after "vc-dispatcher" (code))
;;; Variables
-(defvar org-macro-templates nil
+(defvar-local org-macro-templates nil
"Alist containing all macro templates in current buffer.
Associations are in the shape of (NAME . TEMPLATE) where NAME
stands for macro's name and template for its replacement value,
@@ -59,48 +69,48 @@ both as strings. This is an internal variable. Do not set it
directly, use instead:
#+MACRO: name template")
-(make-variable-buffer-local 'org-macro-templates)
-
;;; Functions
(defun org-macro--collect-macros ()
"Collect macro definitions in current buffer and setup files.
Return an alist containing all macro templates found."
- (let* (collect-macros ; For byte-compiler.
- (collect-macros
- (lambda (files templates)
- ;; Return an alist of macro templates. FILES is a list of
- ;; setup files names read so far, used to avoid circular
- ;; dependencies. TEMPLATES is the alist collected so far.
- (let ((case-fold-search t))
- (org-with-wide-buffer
- (goto-char (point-min))
- (while (re-search-forward
- "^[ \t]*#\\+\\(MACRO\\|SETUPFILE\\):" nil t)
- (let ((element (org-element-at-point)))
- (when (eq (org-element-type element) 'keyword)
- (let ((val (org-element-property :value element)))
- (if (equal (org-element-property :key element) "MACRO")
- ;; Install macro in TEMPLATES.
- (when (string-match
- "^\\(.*?\\)\\(?:\\s-+\\(.*\\)\\)?\\s-*$" val)
- (let* ((name (match-string 1 val))
- (template (or (match-string 2 val) ""))
- (old-cell (assoc name templates)))
- (if old-cell (setcdr old-cell template)
- (push (cons name template) templates))))
- ;; Enter setup file.
- (let ((file (expand-file-name
- (org-remove-double-quotes val))))
- (unless (member file files)
- (with-temp-buffer
- (org-mode)
- (insert (org-file-contents file 'noerror))
- (setq templates
- (funcall collect-macros (cons file files)
- templates)))))))))))
- templates))))
+ (letrec ((collect-macros
+ (lambda (files templates)
+ ;; Return an alist of macro templates. FILES is a list
+ ;; of setup files names read so far, used to avoid
+ ;; circular dependencies. TEMPLATES is the alist
+ ;; collected so far.
+ (let ((case-fold-search t))
+ (org-with-wide-buffer
+ (goto-char (point-min))
+ (while (re-search-forward
+ "^[ \t]*#\\+\\(MACRO\\|SETUPFILE\\):" nil t)
+ (let ((element (org-element-at-point)))
+ (when (eq (org-element-type element) 'keyword)
+ (let ((val (org-element-property :value element)))
+ (if (equal (org-element-property :key element) "MACRO")
+ ;; Install macro in TEMPLATES.
+ (when (string-match
+ "^\\(.*?\\)\\(?:\\s-+\\(.*\\)\\)?\\s-*$" val)
+ (let* ((name (match-string 1 val))
+ (template (or (match-string 2 val) ""))
+ (old-cell (assoc name templates)))
+ (if old-cell (setcdr old-cell template)
+ (push (cons name template) templates))))
+ ;; Enter setup file.
+ (let ((file (expand-file-name
+ (org-unbracket-string "\"" "\"" val))))
+ (unless (member file files)
+ (with-temp-buffer
+ (setq default-directory
+ (file-name-directory file))
+ (org-mode)
+ (insert (org-file-contents file 'noerror))
+ (setq templates
+ (funcall collect-macros (cons file files)
+ templates)))))))))))
+ templates))))
(funcall collect-macros nil nil)))
(defun org-macro-initialize-templates ()
@@ -117,15 +127,26 @@ function installs the following ones: \"property\",
(if old-template (setcdr old-template (cdr cell))
(push cell templates))))))
;; Install hard-coded macros.
- (mapc (lambda (cell) (funcall update-templates cell))
- (list (cons "property" "(eval (org-entry-get nil \"$1\" 'selective))")
+ (mapc update-templates
+ (list (cons "property"
+ "(eval (save-excursion
+ (let ((l \"$2\"))
+ (when (org-string-nw-p l)
+ (condition-case _
+ (let ((org-link-search-must-match-exact-headline t))
+ (org-link-search l nil t))
+ (error
+ (error \"Macro property failed: cannot find location %s\"
+ l)))))
+ (org-entry-get nil \"$1\" 'selective)))")
(cons "time" "(eval (format-time-string \"$1\"))")))
(let ((visited-file (buffer-file-name (buffer-base-buffer))))
(when (and visited-file (file-exists-p visited-file))
- (mapc (lambda (cell) (funcall update-templates cell))
+ (mapc update-templates
(list (cons "input-file" (file-name-nondirectory visited-file))
(cons "modification-time"
- (format "(eval (format-time-string \"$1\" '%s))"
+ (format "(eval (format-time-string \"$1\" (or (and (org-string-nw-p \"$2\") (org-macro--vc-modified-time %s)) '%s)))"
+ (prin1-to-string visited-file)
(prin1-to-string
(nth 5 (file-attributes visited-file)))))))))
(setq org-macro-templates templates)))
@@ -154,38 +175,132 @@ default value. Return nil if no template was found."
;; Return string.
(format "%s" (or value ""))))))
-(defun org-macro-replace-all (templates)
+(defun org-macro-replace-all (templates &optional finalize keywords)
"Replace all macros in current buffer by their expansion.
+
TEMPLATES is an alist of templates used for expansion. See
-`org-macro-templates' for a buffer-local default value."
+`org-macro-templates' for a buffer-local default value.
+
+If optional arg FINALIZE is non-nil, raise an error if a macro is
+found in the buffer with no definition in TEMPLATES.
+
+Optional argument KEYWORDS, when non-nil is a list of keywords,
+as strings, where macro expansion is allowed."
(save-excursion
(goto-char (point-min))
- (let (record)
+ (let ((properties-regexp
+ (format "\\`EXPORT_%s\\+?\\'" (regexp-opt keywords)))
+ record)
(while (re-search-forward "{{{[-A-Za-z0-9_]" nil t)
- (let ((object (org-element-context)))
- (when (eq (org-element-type object) 'macro)
- (let* ((value (org-macro-expand object templates))
- (begin (org-element-property :begin object))
- (signature (list begin
- object
- (org-element-property :args object))))
- ;; Avoid circular dependencies by checking if the same
- ;; macro with the same arguments is expanded at the same
- ;; position twice.
- (if (member signature record)
- (error "Circular macro expansion: %s"
- (org-element-property :key object))
- (when value
- (push signature record)
- (delete-region
- begin
- ;; Preserve white spaces after the macro.
- (progn (goto-char (org-element-property :end object))
- (skip-chars-backward " \t")
- (point)))
- ;; Leave point before replacement in case of recursive
- ;; expansions.
- (save-excursion (insert value)))))))))))
+ (unless (save-match-data (org-in-commented-heading-p))
+ (let* ((datum (save-match-data (org-element-context)))
+ (type (org-element-type datum))
+ (macro
+ (cond
+ ((eq type 'macro) datum)
+ ;; In parsed keywords and associated node
+ ;; properties, force macro recognition.
+ ((or (and (eq type 'keyword)
+ (member (org-element-property :key datum)
+ keywords))
+ (and (eq type 'node-property)
+ (string-match-p properties-regexp
+ (org-element-property :key
+ datum))))
+ (save-excursion
+ (goto-char (match-beginning 0))
+ (org-element-macro-parser))))))
+ (when macro
+ (let* ((value (org-macro-expand macro templates))
+ (begin (org-element-property :begin macro))
+ (signature (list begin
+ macro
+ (org-element-property :args macro))))
+ ;; Avoid circular dependencies by checking if the same
+ ;; macro with the same arguments is expanded at the
+ ;; same position twice.
+ (cond ((member signature record)
+ (error "Circular macro expansion: %s"
+ (org-element-property :key macro)))
+ (value
+ (push signature record)
+ (delete-region
+ begin
+ ;; Preserve white spaces after the macro.
+ (progn (goto-char (org-element-property :end macro))
+ (skip-chars-backward " \t")
+ (point)))
+ ;; Leave point before replacement in case of
+ ;; recursive expansions.
+ (save-excursion (insert value)))
+ (finalize
+ (error "Undefined Org macro: %s; aborting"
+ (org-element-property :key macro))))))))))))
+
+(defun org-macro-escape-arguments (&rest args)
+ "Build macro's arguments string from ARGS.
+ARGS are strings. Return value is a string with arguments
+properly escaped and separated with commas. This is the opposite
+of `org-macro-extract-arguments'."
+ (let ((s ""))
+ (dolist (arg (reverse args) (substring s 1))
+ (setq s
+ (concat
+ ","
+ (replace-regexp-in-string
+ "\\(\\\\*\\),"
+ (lambda (m)
+ (concat (make-string (1+ (* 2 (length (match-string 1 m)))) ?\\)
+ ","))
+ ;; If a non-terminal argument ends on backslashes, make
+ ;; sure to also escape them as they will be followed by
+ ;; a comma.
+ (concat arg (and (not (equal s ""))
+ (string-match "\\\\+\\'" arg)
+ (match-string 0 arg)))
+ nil t)
+ s)))))
+
+(defun org-macro-extract-arguments (s)
+ "Extract macro arguments from string S.
+S is a string containing comma separated values properly escaped.
+Return a list of arguments, as strings. This is the opposite of
+`org-macro-escape-arguments'."
+ ;; 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" ","))))
+ s nil t)
+ "\000"))
+
+(defun org-macro--vc-modified-time (file)
+ (save-window-excursion
+ (when (vc-backend file)
+ (let ((buf (get-buffer-create " *org-vc*"))
+ (case-fold-search t)
+ date)
+ (unwind-protect
+ (progn
+ (vc-call print-log file buf nil nil 1)
+ (with-current-buffer buf
+ (vc-exec-after
+ (lambda ()
+ (goto-char (point-min))
+ (when (re-search-forward "Date:?[ \t]*" nil t)
+ (let ((time (parse-time-string
+ (buffer-substring
+ (point) (line-end-position)))))
+ (when (cl-some #'identity time)
+ (setq date (apply #'encode-time time))))))))
+ (let ((proc (get-buffer-process buf)))
+ (while (and proc (accept-process-output proc .5 nil t)))))
+ (kill-buffer buf))
+ date))))
(provide 'org-macro)
diff --git a/lisp/org/org-macs.el b/lisp/org/org-macs.el
index 64e28cee04c..ca47e5a5a33 100644
--- a/lisp/org/org-macs.el
+++ b/lisp/org/org-macs.el
@@ -1,4 +1,4 @@
-;;; org-macs.el --- Top-level definitions for Org-mode
+;;; org-macs.el --- Top-level Definitions for Org -*- lexical-binding: t; -*-
;; Copyright (C) 2004-2017 Free Software Foundation, Inc.
@@ -25,29 +25,12 @@
;;; Commentary:
;; This file contains macro definitions, defsubst definitions, other
-;; stuff needed for compilation and top-level forms in Org-mode, as well
-;; lots of small functions that are not org-mode specific but simply
-;; generally useful stuff.
+;; stuff needed for compilation and top-level forms in Org mode, as
+;; well lots of small functions that are not Org mode specific but
+;; simply generally useful stuff.
;;; Code:
-(eval-and-compile
- (unless (fboundp 'declare-function)
- (defmacro declare-function (fn file &optional _arglist _fileonly)
- `(autoload ',fn ,file)))
-
- (if (>= emacs-major-version 23)
- (defsubst org-char-to-string(c)
- "Defsubst to decode UTF-8 character values in emacs 23 and beyond."
- (char-to-string c))
- (defsubst org-char-to-string (c)
- "Defsubst to decode UTF-8 character values in emacs 22."
- (string (decode-char 'ucs c)))))
-
-(declare-function org-add-props "org-compat" (string plist &rest props))
-(declare-function org-string-match-p "org-compat"
- (regexp string &optional start))
-
(defmacro org-with-gensyms (symbols &rest body)
(declare (debug (sexp body)) (indent 1))
`(let ,(mapcar (lambda (s)
@@ -55,26 +38,11 @@
symbols)
,@body))
-(defmacro org-called-interactively-p (&optional kind)
- (declare (debug (&optional ("quote" symbolp)))) ;Why not just t?
- (if (featurep 'xemacs)
- `(interactive-p)
- (if (or (> emacs-major-version 23)
- (and (>= emacs-major-version 23)
- (>= emacs-minor-version 2)))
- ;; defined with no argument in <=23.1
- `(with-no-warnings (called-interactively-p ,kind))
- `(interactive-p))))
-
-(defmacro org-bound-and-true-p (var)
- "Return the value of symbol VAR if it is bound, else nil."
- (declare (debug (symbolp)))
- `(and (boundp (quote ,var)) ,var))
-
(defun org-string-nw-p (s)
- "Is S a string with a non-white character?"
+ "Return S if S is a string containing a non-blank character.
+Otherwise, return nil."
(and (stringp s)
- (org-string-match-p "\\S-" s)
+ (string-match-p "[^ \r\t\n]" s)
s))
(defun org-not-nil (v)
@@ -82,25 +50,6 @@
Otherwise return nil."
(and v (not (equal v "nil")) v))
-(defun org-substitute-posix-classes (re)
- "Substitute posix classes in regular expression RE."
- (let ((ss re))
- (save-match-data
- (while (string-match "\\[:alnum:\\]" ss)
- (setq ss (replace-match "a-zA-Z0-9" t t ss)))
- (while (string-match "\\[:word:\\]" ss)
- (setq ss (replace-match "a-zA-Z0-9" t t ss)))
- (while (string-match "\\[:alpha:\\]" ss)
- (setq ss (replace-match "a-zA-Z" t t ss)))
- (while (string-match "\\[:punct:\\]" ss)
- (setq ss (replace-match "\001-@[-`{-~" t t ss)))
- ss)))
-
-(defmacro org-re (s)
- "Replace posix classes in regular expression."
- (declare (debug (form)))
- (if (featurep 'xemacs) `(org-substitute-posix-classes ,s) s))
-
(defmacro org-preserve-lc (&rest body)
(declare (debug (body)))
(org-with-gensyms (line col)
@@ -136,19 +85,6 @@ Otherwise return nil."
(partial-completion-mode 1))
,@body))
-;; FIXME: Slated for removal. Current Org mode does not support Emacs < 22
-(defmacro org-maybe-intangible (props)
- "Add (intangible t) to PROPS if Emacs version is earlier than Emacs 22.
-In Emacs 21, invisible text is not avoided by the command loop, so the
-intangible property is needed to make sure point skips this text.
-In Emacs 22, this is not necessary. The intangible text property has
-led to problems with flyspell. These problems are fixed in flyspell.el,
-but we still avoid setting the property in Emacs 22 and later.
-We use a macro so that the test can happen at compilation time."
- (if (< emacs-major-version 22)
- `(append '(intangible t) ,props)
- props))
-
(defmacro org-with-point-at (pom &rest body)
"Move to buffer and point of point-or-marker POM for the duration of BODY."
(declare (debug (form body)) (indent 1))
@@ -160,10 +96,6 @@ We use a macro so that the test can happen at compilation time."
(goto-char (or ,mpom (point)))
,@body)))))
-(defmacro org-no-warnings (&rest body)
- (declare (debug (body)))
- (cons (if (fboundp 'with-no-warnings) 'with-no-warnings 'progn) body))
-
(defmacro org-with-remote-undo (buffer &rest body)
"Execute BODY while recording undo information in two buffers."
(declare (debug (form body)) (indent 1))
@@ -199,22 +131,12 @@ We use a macro so that the test can happen at compilation time."
org-emphasis t)
"Properties to remove when a string without properties is wanted.")
-(defsubst org-match-string-no-properties (num &optional string)
- (if (featurep 'xemacs)
- (let ((s (match-string num string)))
- (and s (remove-text-properties 0 (length s) org-rm-props s))
- s)
- (match-string-no-properties num string)))
-
(defsubst org-no-properties (s &optional restricted)
"Remove all text properties from string S.
When RESTRICTED is non-nil, only remove the properties listed
in `org-rm-props'."
- (if (fboundp 'set-text-properties)
- (set-text-properties 0 (length s) nil s)
- (if restricted
- (remove-text-properties 0 (length s) org-rm-props s)
- (set-text-properties 0 (length s) nil s)))
+ (if restricted (remove-text-properties 0 (length s) org-rm-props s)
+ (set-text-properties 0 (length s) nil s))
s)
(defsubst org-get-alist-option (option key)
@@ -236,16 +158,6 @@ program is needed for, so that the error message can be more informative."
(error "Can't find `%s'%s" cmd
(if use (format " (%s)" use) "")))))
-(defsubst org-inhibit-invisibility ()
- "Modified `buffer-invisibility-spec' for Emacs 21.
-Some ops with invisible text do not work correctly on Emacs 21. For these
-we turn off invisibility temporarily. Use this in a `let' form."
- (if (< emacs-major-version 22) nil buffer-invisibility-spec))
-
-(defsubst org-set-local (var value)
- "Make VAR local in current buffer and set it to VALUE."
- (set (make-local-variable var) value))
-
(defsubst org-last (list)
"Return the last element of LIST."
(car (last list)))
@@ -282,11 +194,11 @@ we turn off invisibility temporarily. Use this in a `let' form."
(<= (match-beginning n) pos)
(>= (match-end n) pos)))
-(defun org-match-line (re)
- "Looking-at at the beginning of the current line."
+(defun org-match-line (regexp)
+ "Match REGEXP at the beginning of the current line."
(save-excursion
- (goto-char (point-at-bol))
- (looking-at re)))
+ (beginning-of-line)
+ (looking-at regexp)))
(defun org-plist-delete (plist property)
"Delete PROPERTY from PLIST.
@@ -298,13 +210,6 @@ This is in contrast to merely setting it to 0."
(setq plist (cddr plist)))
p))
-(defun org-replace-match-keep-properties (newtext &optional fixedcase
- literal string)
- "Like `replace-match', but add the text properties found original text."
- (setq newtext (org-add-props newtext (text-properties-at
- (match-beginning 0) string)))
- (replace-match newtext fixedcase literal string))
-
(defmacro org-save-outline-visibility (use-markers &rest body)
"Save and restore outline visibility around BODY.
If USE-MARKERS is non-nil, use markers for the positions.
@@ -313,19 +218,15 @@ but it also means that the buffer should stay alive
during the operation, because otherwise all these markers will
point nowhere."
(declare (debug (form body)) (indent 1))
- (org-with-gensyms (data rtn)
- `(let ((,data (org-outline-overlay-data ,use-markers))
- ,rtn)
+ (org-with-gensyms (data)
+ `(let ((,data (org-outline-overlay-data ,use-markers)))
(unwind-protect
- (progn
- (setq ,rtn (progn ,@body))
+ (prog1 (progn ,@body)
(org-set-outline-overlay-data ,data))
(when ,use-markers
- (mapc (lambda (c)
- (and (markerp (car c)) (move-marker (car c) nil))
- (and (markerp (cdr c)) (move-marker (cdr c) nil)))
- ,data)))
- ,rtn)))
+ (dolist (c ,data)
+ (when (markerp (car c)) (move-marker (car c) nil))
+ (when (markerp (cdr c)) (move-marker (cdr c) nil))))))))
(defmacro org-with-wide-buffer (&rest body)
"Execute body while temporarily widening the buffer."
@@ -355,17 +256,16 @@ point nowhere."
(defun org-get-limited-outline-regexp ()
"Return outline-regexp with limited number of levels.
The number of levels is controlled by `org-inlinetask-min-level'"
- (if (or (not (derived-mode-p 'org-mode)) (not (featurep 'org-inlinetask)))
- org-outline-regexp
- (let* ((limit-level (1- org-inlinetask-min-level))
- (nstars (if org-odd-levels-only (1- (* limit-level 2)) limit-level)))
- (format "\\*\\{1,%d\\} " nstars))))
-
-(defun org-format-seconds (string seconds)
- "Compatibility function replacing format-seconds."
- (if (fboundp 'format-seconds)
- (format-seconds string seconds)
- (format-time-string string (seconds-to-time seconds))))
+ (cond ((not (derived-mode-p 'org-mode))
+ outline-regexp)
+ ((not (featurep 'org-inlinetask))
+ org-outline-regexp)
+ (t
+ (let* ((limit-level (1- org-inlinetask-min-level))
+ (nstars (if org-odd-levels-only
+ (1- (* limit-level 2))
+ limit-level)))
+ (format "\\*\\{1,%d\\} " nstars)))))
(defmacro org-eval-in-environment (environment form)
(declare (debug (form form)) (indent 1))
@@ -382,10 +282,27 @@ the value in cdr."
;;;###autoload
(defmacro org-load-noerror-mustsuffix (file)
- "Load FILE with optional arguments NOERROR and MUSTSUFFIX. Drop the MUSTSUFFIX argument for XEmacs, which doesn't recognize it."
- (if (featurep 'xemacs)
- `(load ,file 'noerror)
- `(load ,file 'noerror nil nil 'mustsuffix)))
+ "Load FILE with optional arguments NOERROR and MUSTSUFFIX."
+ `(load ,file 'noerror nil nil 'mustsuffix))
+
+(defun org-unbracket-string (pre post string)
+ "Remove PRE/POST from the beginning/end of STRING.
+Both PRE and POST must be pre-/suffixes of STRING, or neither is
+removed."
+ (if (and (string-prefix-p pre string)
+ (string-suffix-p post string))
+ (substring string (length pre) (- (length post)))
+ string))
+
+(defun org-read-function (prompt &optional allow-empty?)
+ "Prompt for a function.
+If ALLOW-EMPTY? is non-nil, return nil rather than raising an
+error when the user input is empty."
+ (let ((func (completing-read prompt obarray #'fboundp t)))
+ (cond ((not (string= func ""))
+ (intern func))
+ (allow-empty? nil)
+ (t (user-error "Empty input is not valid")))))
(provide 'org-macs)
diff --git a/lisp/org/org-mhe.el b/lisp/org/org-mhe.el
index 72c2eeec22e..4142ae45b2b 100644
--- a/lisp/org/org-mhe.el
+++ b/lisp/org/org-mhe.el
@@ -1,4 +1,4 @@
-;;; org-mhe.el --- Support for links to MH-E messages from within Org-mode
+;;; org-mhe.el --- Support for Links to MH-E Messages -*- lexical-binding: t; -*-
;; Copyright (C) 2004-2017 Free Software Foundation, Inc.
@@ -24,8 +24,8 @@
;;
;;; Commentary:
-;; This file implements links to MH-E messages from within Org-mode.
-;; Org-mode loads this module by default - if this is not what you want,
+;; This file implements links to MH-E messages from within Org.
+;; Org mode loads this module by default - if this is not what you want,
;; configure the variable `org-modules'.
;;; Code:
@@ -74,34 +74,25 @@ supported by MH-E."
(defvar mh-search-regexp-builder)
;; Install the link type
-(org-add-link-type "mhe" 'org-mhe-open)
-(add-hook 'org-store-link-functions 'org-mhe-store-link)
+(org-link-set-parameters "mhe" :follow #'org-mhe-open :store #'org-mhe-store-link)
;; Implementation
(defun org-mhe-store-link ()
"Store a link to an MH-E folder or message."
- (when (or (equal major-mode 'mh-folder-mode)
- (equal major-mode 'mh-show-mode))
+ (when (or (eq major-mode 'mh-folder-mode)
+ (eq major-mode 'mh-show-mode))
(save-window-excursion
(let* ((from (org-mhe-get-header "From:"))
(to (org-mhe-get-header "To:"))
(message-id (org-mhe-get-header "Message-Id:"))
(subject (org-mhe-get-header "Subject:"))
(date (org-mhe-get-header "Date:"))
- (date-ts (and date (format-time-string
- (org-time-stamp-format t) (date-to-time date))))
- (date-ts-ia (and date (format-time-string
- (org-time-stamp-format t t)
- (date-to-time date))))
link desc)
- (org-store-link-props :type "mh" :from from :to to
+ (org-store-link-props :type "mh" :from from :to to :date date
:subject subject :message-id message-id)
- (when date
- (org-add-link-props :date date :date-timestamp date-ts
- :date-timestamp-inactive date-ts-ia))
(setq desc (org-email-link-description))
(setq link (concat "mhe:" (org-mhe-get-message-real-folder) "#"
- (org-remove-angle-brackets message-id)))
+ (org-unbracket-string "<" ">" message-id)))
(org-add-link-props :link link :description desc)
link))))
@@ -120,7 +111,7 @@ supported by MH-E."
So if you use sequences, it will now work."
(save-excursion
(let* ((folder
- (if (equal major-mode 'mh-folder-mode)
+ (if (eq major-mode 'mh-folder-mode)
mh-current-folder
;; Refer to the show buffer
mh-show-folder-buffer))
@@ -132,7 +123,7 @@ So if you use sequences, it will now work."
;; mh-index-data is always nil in a show buffer.
(if (and (boundp 'mh-index-folder)
(string= mh-index-folder (substring folder 0 end-index)))
- (if (equal major-mode 'mh-show-mode)
+ (if (eq major-mode 'mh-show-mode)
(save-window-excursion
(let (pop-up-frames)
(when (buffer-live-p (get-buffer folder))
@@ -158,7 +149,7 @@ So if you use sequences, it will now work."
"Return the name of the current message folder.
Be careful if you use sequences."
(save-excursion
- (if (equal major-mode 'mh-folder-mode)
+ (if (eq major-mode 'mh-folder-mode)
mh-current-folder
;; Refer to the show buffer
mh-show-folder-buffer)))
@@ -167,7 +158,7 @@ Be careful if you use sequences."
"Return the number of the current message.
Be careful if you use sequences."
(save-excursion
- (if (equal major-mode 'mh-folder-mode)
+ (if (eq major-mode 'mh-folder-mode)
(mh-get-msg-num nil)
;; Refer to the show buffer
(mh-show-buffer-message-number))))
@@ -182,12 +173,12 @@ you have a better idea of how to do this then please let us know."
(header-field))
(with-current-buffer buffer
(mh-display-msg num folder)
- (if (equal major-mode 'mh-folder-mode)
+ (if (eq major-mode 'mh-folder-mode)
(mh-header-display)
(mh-show-header-display))
(set-buffer buffer)
(setq header-field (mh-get-header-field header))
- (if (equal major-mode 'mh-folder-mode)
+ (if (eq major-mode 'mh-folder-mode)
(mh-show)
(mh-show-show))
(org-trim header-field))))
@@ -206,13 +197,13 @@ folders."
(if (not article)
(mh-visit-folder (mh-normalize-folder-name folder))
(mh-search-choose)
- (if (equal mh-searcher 'pick)
+ (if (eq mh-searcher 'pick)
(progn
(setq article (org-add-angle-brackets article))
(mh-search folder (list "--message-id" article))
(when (and org-mhe-search-all-folders
(not (org-mhe-get-message-real-folder)))
- (kill-this-buffer)
+ (kill-buffer)
(mh-search "+" (list "--message-id" article))))
(if mh-search-regexp-builder
(mh-search "+" (funcall mh-search-regexp-builder
@@ -220,7 +211,7 @@ folders."
(mh-search "+" article)))
(if (org-mhe-get-message-real-folder)
(mh-show-msg 1)
- (kill-this-buffer)
+ (kill-buffer)
(error "Message not found"))))
(provide 'org-mhe)
diff --git a/lisp/org/org-mobile.el b/lisp/org/org-mobile.el
index 34e6af10d81..12e6c84b3ce 100644
--- a/lisp/org/org-mobile.el
+++ b/lisp/org/org-mobile.el
@@ -1,4 +1,4 @@
-;;; org-mobile.el --- Code for asymmetric sync with a mobile device
+;;; org-mobile.el --- Code for Asymmetric Sync With a Mobile Device -*- lexical-binding: t; -*-
;; Copyright (C) 2009-2017 Free Software Foundation, Inc.
;;
;; Author: Carsten Dominik
@@ -24,21 +24,20 @@
;;
;;; Commentary:
;;
-;; This file contains the code to interact with Richard Moreland's iPhone
-;; application MobileOrg, as well as with the Android version by Matthew Jones.
-;; This code is documented in Appendix B of the Org-mode manual. The code is
-;; not specific for the iPhone and Android - any external
-;; viewer/flagging/editing application that uses the same conventions could
-;; be used.
+;; This file contains the code to interact with Richard Moreland's
+;; iPhone application MobileOrg, as well as with the Android version
+;; by Matthew Jones. This code is documented in Appendix B of the Org
+;; manual. The code is not specific for the iPhone and Android - any
+;; external viewer/flagging/editing application that uses the same
+;; conventions could be used.
(require 'org)
(require 'org-agenda)
-;;; Code:
+(require 'cl-lib)
-(eval-when-compile (require 'cl))
+(defvar org-agenda-keep-restricted-file-list)
-(declare-function org-pop-to-buffer-same-window
- "org-compat" (&optional buffer-or-name norecord label))
+;;; Code:
(defgroup org-mobile nil
"Options concerning support for a viewer/editor on a mobile device."
@@ -192,27 +191,6 @@ the editing types for which the mobile version should always dominate."
(const heading)
(const body))))
-(defcustom org-mobile-action-alist
- '(("edit" . (org-mobile-edit data old new)))
- "Alist with flags and actions for mobile sync.
-When flagging an entry, MobileOrg will create entries that look like
-
- * F(action:data) [[id:entry-id][entry title]]
-
-This alist defines that the ACTION in the parentheses of F() should mean,
-i.e. what action should be taken. The :data part in the parenthesis is
-optional. If present, the string after the colon will be passed to the
-action form as the `data' variable.
-The car of each elements of the alist is an actions string. The cdr is
-an Emacs Lisp form that will be evaluated with the cursor on the headline
-of that entry.
-
-For now, it is not recommended to change this variable."
- :group 'org-mobile
- :type '(repeat
- (cons (string :tag "Action flag")
- (sexp :tag "Action form"))))
-
(defcustom org-mobile-checksum-binary (or (executable-find "shasum")
(executable-find "sha1sum")
(executable-find "md5sum")
@@ -249,6 +227,23 @@ by the mobile device, this hook should be used to copy the emptied
capture file `mobileorg.org' back to the WebDAV directory, for example
using `rsync' or `scp'.")
+(defconst org-mobile-action-alist '(("edit" . org-mobile-edit))
+ "Alist with flags and actions for mobile sync.
+When flagging an entry, MobileOrg will create entries that look like
+
+ * F(action:data) [[id:entry-id][entry title]]
+
+This alist defines that the ACTION in the parentheses of F()
+should mean, i.e. what action should be taken. The :data part in
+the parenthesis is optional. If present, the string after the
+colon will be passed to the action function as the first argument
+variable.
+
+The car of each elements of the alist is an actions string. The
+cdr is a function that is called with the cursor on the headline
+of that entry. It should accept three arguments, the :data part,
+the old and new values for the entry.")
+
(defvar org-mobile-last-flagged-files nil
"List of files containing entries flagged in the latest pull.")
@@ -313,40 +308,29 @@ Also exclude files matching `org-mobile-files-exclude-regexp'."
This will create the index file, copy all agenda files there, and also
create all custom agenda views, for upload to the mobile phone."
(interactive)
- (let ((a-buffer (get-buffer org-agenda-buffer-name)))
- (let ((org-agenda-curbuf-name org-agenda-buffer-name)
- (org-agenda-buffer-name "*SUMO*")
- (org-agenda-tag-filter org-agenda-tag-filter)
- (org-agenda-redo-command org-agenda-redo-command))
- (save-excursion
- (save-restriction
- (save-window-excursion
- (run-hooks 'org-mobile-pre-push-hook)
- (org-mobile-check-setup)
- (org-mobile-prepare-file-lists)
- (message "Creating agendas...")
- (let ((inhibit-redisplay t)
- (org-agenda-files (mapcar 'car org-mobile-files-alist)))
- (org-mobile-create-sumo-agenda))
- (message "Creating agendas...done")
- (org-save-all-org-buffers) ; to save any IDs created by this process
- (message "Copying files...")
- (org-mobile-copy-agenda-files)
- (message "Writing index file...")
- (org-mobile-create-index-file)
- (message "Writing checksums...")
- (org-mobile-write-checksums)
- (run-hooks 'org-mobile-post-push-hook))))
- (setq org-agenda-buffer-name org-agenda-curbuf-name
- org-agenda-this-buffer-name org-agenda-curbuf-name))
- (redraw-display)
- (when (buffer-live-p a-buffer)
- (if (not (get-buffer-window a-buffer))
- (kill-buffer a-buffer)
- (let ((cw (selected-window)))
- (select-window (get-buffer-window a-buffer))
- (org-agenda-redo)
- (select-window cw)))))
+ (let ((org-agenda-buffer-name "*SUMO*")
+ (org-agenda-tag-filter org-agenda-tag-filter)
+ (org-agenda-redo-command org-agenda-redo-command))
+ (save-excursion
+ (save-restriction
+ (save-window-excursion
+ (run-hooks 'org-mobile-pre-push-hook)
+ (org-mobile-check-setup)
+ (org-mobile-prepare-file-lists)
+ (message "Creating agendas...")
+ (let ((inhibit-redisplay t)
+ (org-agenda-files (mapcar 'car org-mobile-files-alist)))
+ (org-mobile-create-sumo-agenda))
+ (message "Creating agendas...done")
+ (org-save-all-org-buffers) ; to save any IDs created by this process
+ (message "Copying files...")
+ (org-mobile-copy-agenda-files)
+ (message "Writing index file...")
+ (org-mobile-create-index-file)
+ (message "Writing checksums...")
+ (org-mobile-write-checksums)
+ (run-hooks 'org-mobile-post-push-hook)))))
+ (org-agenda-maybe-redo)
(message "Files for mobile viewer staged"))
(defvar org-mobile-before-process-capture-hook nil
@@ -422,10 +406,10 @@ agenda view showing the flagged items."
(let ((files-alist (sort (copy-sequence org-mobile-files-alist)
(lambda (a b) (string< (cdr a) (cdr b)))))
(def-todo (default-value 'org-todo-keywords))
- (def-tags (default-value 'org-tag-alist))
+ (def-tags org-tag-alist)
(target-file (expand-file-name org-mobile-index-file
org-mobile-directory))
- file link-name todo-kwds done-kwds tags drawers entry kwds dwds twds)
+ todo-kwds done-kwds tags)
(when (stringp (car def-todo))
(setq def-todo (list (cons 'sequence def-todo))))
(org-agenda-prepare-buffers (mapcar 'car files-alist))
@@ -433,52 +417,36 @@ agenda view showing the flagged items."
(setq todo-kwds (org-delete-all
done-kwds
(org-uniquify org-todo-keywords-for-agenda)))
- (setq drawers (org-uniquify org-drawers-for-agenda))
(setq tags (mapcar 'car (org-global-tags-completion-table
(mapcar 'car files-alist))))
- (with-temp-file
- (if org-mobile-use-encryption
- org-mobile-encryption-tempfile
- target-file)
- (while (setq entry (pop def-todo))
- (insert "#+READONLY\n")
- (setq kwds (mapcar (lambda (x) (if (string-match "(" x)
- (substring x 0 (match-beginning 0))
- x))
- (cdr entry)))
- (insert "#+TODO: " (mapconcat 'identity kwds " ") "\n")
- (setq dwds (member "|" kwds)
- twds (org-delete-all dwds kwds)
- todo-kwds (org-delete-all twds todo-kwds)
- done-kwds (org-delete-all dwds done-kwds)))
+ (with-temp-file (if org-mobile-use-encryption org-mobile-encryption-tempfile
+ target-file)
+ (insert "#+READONLY\n")
+ (dolist (entry def-todo)
+ (let ((kwds (mapcar (lambda (x)
+ (if (string-match "(" x)
+ (substring x 0 (match-beginning 0))
+ x))
+ (cdr entry))))
+ (insert "#+TODO: " (mapconcat #'identity kwds " ") "\n")
+ (let* ((dwds (or (member "|" kwds) (last kwds)))
+ (twds (org-delete-all dwds kwds)))
+ (setq todo-kwds (org-delete-all twds todo-kwds))
+ (setq done-kwds (org-delete-all dwds done-kwds)))))
(when (or todo-kwds done-kwds)
(insert "#+TODO: " (mapconcat 'identity todo-kwds " ") " | "
(mapconcat 'identity done-kwds " ") "\n"))
- (setq def-tags (mapcar
- (lambda (x)
- (cond ((null x) nil)
- ((stringp x) x)
- ((eq (car x) :startgroup) "{")
- ((eq (car x) :endgroup) "}")
- ((eq (car x) :grouptags) nil)
- ((eq (car x) :newline) nil)
- ((listp x) (car x))))
- def-tags))
- (setq def-tags (delq nil def-tags))
+ (setq def-tags (split-string (org-tag-alist-to-string def-tags t)))
(setq tags (org-delete-all def-tags tags))
(setq tags (sort tags (lambda (a b) (string< (downcase a) (downcase b)))))
(setq tags (append def-tags tags nil))
(insert "#+TAGS: " (mapconcat 'identity tags " ") "\n")
- (insert "#+DRAWERS: " (mapconcat 'identity drawers " ") "\n")
(insert "#+ALLPRIORITIES: " org-mobile-allpriorities "\n")
(when (file-exists-p (expand-file-name
org-mobile-directory "agendas.org"))
(insert "* [[file:agendas.org][Agenda Views]]\n"))
- (while (setq entry (pop files-alist))
- (setq file (car entry)
- link-name (cdr entry))
- (insert (format "* [[file:%s][%s]]\n"
- link-name link-name)))
+ (pcase-dolist (`(,_ . ,link-name) files-alist)
+ (insert (format "* [[file:%s][%s]]\n" link-name link-name)))
(push (cons org-mobile-index-file (md5 (buffer-string)))
org-mobile-checksum-files))
(when org-mobile-use-encryption
@@ -501,7 +469,8 @@ agenda view showing the flagged items."
(org-mobile-encrypt-and-move file target-path)
(copy-file file target-path 'ok-if-exists))
(setq check (shell-command-to-string
- (concat org-mobile-checksum-binary " "
+ (concat (shell-quote-argument org-mobile-checksum-binary)
+ " "
(shell-quote-argument (expand-file-name file)))))
(when (string-match "[a-fA-F0-9]\\{30,40\\}" check)
(push (cons link-name (match-string 0 check))
@@ -663,7 +632,7 @@ The table of checksums is written to the file mobile-checksums."
m 10 " " 'planning)
"\n")
(when (setq id
- (if (org-bound-and-true-p
+ (if (bound-and-true-p
org-mobile-force-id-on-agenda-items)
(org-id-get m 'create)
(or (org-entry-get m "ID")
@@ -679,7 +648,7 @@ The table of checksums is written to the file mobile-checksums."
(org-with-point-at pom
(concat "olp:"
(org-mobile-escape-olp (file-name-nondirectory buffer-file-name))
- "/"
+ ":"
(mapconcat 'org-mobile-escape-olp
(org-get-outline-path)
"/")
@@ -823,14 +792,14 @@ If BEG and END are given, only do this in that region."
(cnt-flag 0)
(cnt-error 0)
buf-list
- id-pos org-mobile-error)
+ org-mobile-error)
;; Count the new captures
(goto-char beg)
(while (re-search-forward "^\\* \\(.*\\)" end t)
(and (>= (- (match-end 1) (match-beginning 1)) 2)
(not (equal (downcase (substring (match-string 1) 0 2)) "f("))
- (incf cnt-new)))
+ (cl-incf cnt-new)))
;; Find and apply the edits
(goto-char beg)
@@ -842,19 +811,21 @@ If BEG and END are given, only do this in that region."
(id-pos (condition-case msg
(org-mobile-locate-entry (match-string 4))
(error (nth 1 msg))))
- (bos (point-at-bol))
+ (bos (line-beginning-position))
(eos (save-excursion (org-end-of-subtree t t)))
(cmd (if (equal action "")
- '(progn
- (incf cnt-flag)
- (org-toggle-tag "FLAGGED" 'on)
- (and note
- (org-entry-put nil "THEFLAGGINGNOTE" note)))
- (incf cnt-edit)
+ (let ((note (buffer-substring-no-properties
+ (line-beginning-position 2) eos)))
+ (lambda (_data _old _new)
+ (cl-incf cnt-flag)
+ (org-toggle-tag "FLAGGED" 'on)
+ (org-entry-put
+ nil "THEFLAGGINGNOTE"
+ (replace-regexp-in-string "\n" "\\\\n" note))))
+ (cl-incf cnt-edit)
(cdr (assoc action org-mobile-action-alist))))
- (note (and (equal action "")
- (buffer-substring (1+ (point-at-eol)) eos)))
- (org-inhibit-logging 'note) ;; Do not take notes interactively
+ ;; Do not take notes interactively.
+ (org-inhibit-logging 'note)
old new)
(goto-char bos)
@@ -867,11 +838,11 @@ If BEG and END are given, only do this in that region."
(if (stringp id-pos)
(insert id-pos " ")
(insert "BAD REFERENCE "))
- (incf cnt-error)
+ (cl-incf cnt-error)
(throw 'next t))
(unless cmd
(insert "BAD FLAG ")
- (incf cnt-error)
+ (cl-incf cnt-error)
(throw 'next t))
(move-marker bos-marker (point))
(if (re-search-forward "^** Old value[ \t]*$" eos t)
@@ -884,34 +855,28 @@ If BEG and END are given, only do this in that region."
(progn (outline-next-heading)
(if (eobp) (org-back-over-empty-lines))
(point)))))
- (setq old (and old (if (string-match "\\S-" old) old nil)))
- (setq new (and new (if (string-match "\\S-" new) new nil)))
- (if (and note (> (length note) 0))
- ;; Make Note into a single line, to fit into a property
- (setq note (mapconcat 'identity
- (org-split-string (org-trim note) "\n")
- "\\n")))
+ (setq old (org-string-nw-p old))
+ (setq new (org-string-nw-p new))
(unless (equal data "body")
- (setq new (and new (org-trim new))
- old (and old (org-trim old))))
+ (setq new (and new (org-trim new)))
+ (setq old (and old (org-trim old))))
(goto-char (+ 2 bos-marker))
;; Remember this place so that we can return
(move-marker marker (point))
(setq org-mobile-error nil)
- (save-excursion
- (condition-case msg
- (org-with-point-at id-pos
- (progn
- (eval cmd)
- (unless (member data (list "delete" "archive" "archive-sibling" "addheading"))
- (if (member "FLAGGED" (org-get-tags))
- (add-to-list 'org-mobile-last-flagged-files
- (buffer-file-name (current-buffer)))))))
- (error (setq org-mobile-error msg))))
+ (condition-case msg
+ (org-with-point-at id-pos
+ (funcall cmd data old new)
+ (unless (member data '("delete" "archive" "archive-sibling"
+ "addheading"))
+ (when (member "FLAGGED" (org-get-tags))
+ (add-to-list 'org-mobile-last-flagged-files
+ (buffer-file-name)))))
+ (error (setq org-mobile-error msg)))
(when org-mobile-error
- (org-pop-to-buffer-same-window (marker-buffer marker))
+ (pop-to-buffer-same-window (marker-buffer marker))
(goto-char marker)
- (incf cnt-error)
+ (cl-incf cnt-error)
(insert (if (stringp (nth 1 org-mobile-error))
(nth 1 org-mobile-error)
"EXECUTION FAILED")
@@ -924,8 +889,8 @@ If BEG and END are given, only do this in that region."
(save-buffer)
(move-marker marker nil)
(move-marker end nil)
- (message "%d new, %d edits, %d flags, %d errors" cnt-new
- cnt-edit cnt-flag cnt-error)
+ (message "%d new, %d edits, %d flags, %d errors"
+ cnt-new cnt-edit cnt-flag cnt-error)
(sit-for 1)))
(defun org-mobile-timestamp-buffer (buf)
@@ -1020,7 +985,7 @@ be returned that indicates what went wrong."
((equal new "DONEARCHIVE")
(org-todo 'done)
(org-archive-subtree-default))
- ((equal new current) t) ; nothing needs to be done
+ ((equal new current) t) ; nothing needs to be done
((or (equal current old)
(eq org-mobile-force-mobile-change t)
(memq 'todo org-mobile-force-mobile-change))
@@ -1042,33 +1007,35 @@ be returned that indicates what went wrong."
(or old "") (or current "")))))
((eq what 'priority)
- (when (looking-at org-complex-heading-regexp)
- (setq current (and (match-end 3) (substring (match-string 3) 2 3)))
- (cond
- ((equal current new) t) ; no action required
- ((or (equal current old)
- (eq org-mobile-force-mobile-change t)
- (memq 'tags org-mobile-force-mobile-change))
- (org-priority (and new (string-to-char new))))
- (t (error "Priority was expected to be %s, but is %s"
- old current)))))
+ (let ((case-fold-search nil))
+ (when (looking-at org-complex-heading-regexp)
+ (let ((current (and (match-end 3) (substring (match-string 3) 2 3))))
+ (cond
+ ((equal current new) t) ;no action required
+ ((or (equal current old)
+ (eq org-mobile-force-mobile-change t)
+ (memq 'tags org-mobile-force-mobile-change))
+ (org-priority (and new (string-to-char new))))
+ (t (error "Priority was expected to be %s, but is %s"
+ old current)))))))
((eq what 'heading)
- (when (looking-at org-complex-heading-regexp)
- (setq current (match-string 4))
- (cond
- ((equal current new) t) ; no action required
- ((or (equal current old)
- (eq org-mobile-force-mobile-change t)
- (memq 'heading org-mobile-force-mobile-change))
- (goto-char (match-beginning 4))
- (insert new)
- (delete-region (point) (+ (point) (length current)))
- (org-set-tags nil 'align))
- (t (error "Heading changed in MobileOrg and on the computer")))))
+ (let ((case-fold-search nil))
+ (when (looking-at org-complex-heading-regexp)
+ (let ((current (match-string 4)))
+ (cond
+ ((equal current new) t) ;no action required
+ ((or (equal current old)
+ (eq org-mobile-force-mobile-change t)
+ (memq 'heading org-mobile-force-mobile-change))
+ (goto-char (match-beginning 4))
+ (insert new)
+ (delete-region (point) (+ (point) (length current)))
+ (org-set-tags nil 'align))
+ (t (error "Heading changed in MobileOrg and on the computer")))))))
((eq what 'addheading)
- (if (org-at-heading-p) ; if false we are in top-level of file
+ (if (org-at-heading-p) ; if false we are in top-level of file
(progn
;; Workaround a `org-insert-heading-respect-content' bug
;; which prevents correct insertion when point is invisible
@@ -1083,7 +1050,7 @@ be returned that indicates what went wrong."
((eq what 'refile)
(org-copy-subtree)
(org-with-point-at (org-mobile-locate-entry new)
- (if (org-at-heading-p) ; if false we are in top-level of file
+ (if (org-at-heading-p) ; if false we are in top-level of file
(progn
(setq level (org-get-valid-level (funcall outline-level) 1))
(org-end-of-subtree t t)
diff --git a/lisp/org/org-mouse.el b/lisp/org/org-mouse.el
index 7eef5c6b8ba..d6a472787e1 100644
--- a/lisp/org/org-mouse.el
+++ b/lisp/org/org-mouse.el
@@ -1,4 +1,4 @@
-;;; org-mouse.el --- Better mouse support for org-mode
+;;; org-mouse.el --- Better mouse support for Org -*- lexical-binding: t; -*-
;; Copyright (C) 2006-2017 Free Software Foundation, Inc.
@@ -26,8 +26,8 @@
;;
;; http://orgmode.org
;;
-;; Org-mouse implements the following features:
-;; * following links with the left mouse button (in Emacs 22)
+;; Org mouse implements the following features:
+;; * following links with the left mouse button
;; * subtree expansion/collapse (org-cycle) with the left mouse button
;; * several context menus on the right mouse button:
;; + general text
@@ -66,12 +66,12 @@
;; History:
;;
-;; Since version 5.10: Changes are listed in the general org-mode docs.
+;; Since version 5.10: Changes are listed in the general Org docs.
;;
-;; Version 5.09;; + Version number synchronization with Org-mode.
+;; Version 5.09;; + Version number synchronization with Org mode.
;;
;; Version 0.25
-;; + made compatible with org-mode 4.70 (thanks to Carsten for the patch)
+;; + made compatible with Org 4.70 (thanks to Carsten for the patch)
;;
;; Version 0.24
;; + minor changes to the table menu
@@ -81,7 +81,7 @@
;; + context menu support for org-agenda-undo & org-sort-entries
;;
;; Version 0.22
-;; + handles undo support for the agenda buffer (requires org-mode >=4.58)
+;; + handles undo support for the agenda buffer (requires Org >=4.58)
;;
;; Version 0.21
;; + selected text activates its context menu
@@ -105,7 +105,7 @@
;; + added support for checkboxes
;;
;; Version 0.15
-;; + org-mode now works with the Agenda buffer as well
+;; + Org now works with the Agenda buffer as well
;;
;; Version 0.14
;; + added a menu option that converts plain list items to outline items
@@ -125,7 +125,7 @@
;;
;; Version 0.10
;; + added a menu option to remove highlights
-;; + compatible with org-mode 4.21 now
+;; + compatible with Org 4.21 now
;;
;; Version 0.08:
;; + trees can be moved/promoted/demoted by dragging with the right
@@ -136,8 +136,8 @@
;;; Code:
-(eval-when-compile (require 'cl))
(require 'org)
+(require 'cl-lib)
(defvar org-agenda-allow-remote-undo)
(defvar org-agenda-undo-list)
@@ -149,6 +149,8 @@
(declare-function org-agenda-earlier "org-agenda" (arg))
(declare-function org-agenda-later "org-agenda" (arg))
+(defvar org-mouse-main-buffer nil
+ "Active buffer for mouse operations.")
(defvar org-mouse-plain-list-regexp "\\([ \t]*\\)\\([-+*]\\|[0-9]+[.)]\\) "
"Regular expression that matches a plain list.")
(defvar org-mouse-direct t
@@ -191,15 +193,14 @@ Changing this variable requires a restart of Emacs to get activated."
(interactive)
(end-of-line)
(skip-chars-backward "\t ")
- (when (org-looking-back ":[A-Za-z]+:" (line-beginning-position))
+ (when (looking-back ":[A-Za-z]+:" (line-beginning-position))
(skip-chars-backward ":A-Za-z")
(skip-chars-backward "\t ")))
-(defvar org-mouse-context-menu-function nil
+(defvar-local org-mouse-context-menu-function nil
"Function to create the context menu.
The value of this variable is the function invoked by
`org-mouse-context-menu' as the context menu.")
-(make-variable-buffer-local 'org-mouse-context-menu-function)
(defun org-mouse-show-context-menu (event prefix)
"Invoke the context menu.
@@ -215,13 +216,12 @@ this function is called. Otherwise, the current major mode menu is used."
(when (not (org-mouse-mark-active))
(goto-char (posn-point (event-start event)))
(when (not (eolp)) (save-excursion (run-hooks 'post-command-hook)))
- (let ((redisplay-dont-pause t))
- (sit-for 0)))
+ (sit-for 0))
(if (functionp org-mouse-context-menu-function)
(funcall org-mouse-context-menu-function event)
(if (fboundp 'mouse-menu-major-mode-map)
(popup-menu (mouse-menu-major-mode-map) event prefix)
- (org-no-warnings ; don't warn about fallback, obsolete since 23.1
+ (with-no-warnings ; don't warn about fallback, obsolete since 23.1
(mouse-major-mode-menu event prefix)))))
(setq this-command 'mouse-save-then-kill)
(mouse-save-then-kill event)))
@@ -258,7 +258,7 @@ If the point is at the :beginning (`org-mouse-line-position') of the line,
insert the new heading before the current line. Otherwise, insert it
after the current heading."
(interactive)
- (case (org-mouse-line-position)
+ (cl-case (org-mouse-line-position)
(:beginning (beginning-of-line)
(org-insert-heading))
(t (org-mouse-next-heading)
@@ -314,10 +314,10 @@ nor a function, elements of KEYWORDS are used directly."
(just-one-space))
(defvar org-mouse-rest)
-(defun org-mouse-replace-match-and-surround (newtext &optional fixedcase
- literal string subexp)
+(defun org-mouse-replace-match-and-surround
+ (_newtext &optional _fixedcase _literal _string subexp)
"The same as `replace-match', but surrounds the replacement with spaces."
- (apply 'replace-match org-mouse-rest)
+ (apply #'replace-match org-mouse-rest)
(save-excursion
(goto-char (match-beginning (or subexp 0)))
(just-one-space)
@@ -407,8 +407,8 @@ SCHEDULED: or DEADLINE: or ANYTHINGLIKETHIS:"
(> (match-end 0) point))))))
(defun org-mouse-priority-list ()
- (loop for priority from ?A to org-lowest-priority
- collect (char-to-string priority)))
+ (cl-loop for priority from ?A to org-lowest-priority
+ collect (char-to-string priority)))
(defun org-mouse-todo-menu (state)
"Create the menu with TODO keywords."
@@ -460,33 +460,33 @@ SCHEDULED: or DEADLINE: or ANYTHINGLIKETHIS:"
(insert " [ ] "))))
(defun org-mouse-agenda-type (type)
- (case type
- ('tags "Tags: ")
- ('todo "TODO: ")
- ('tags-tree "Tags tree: ")
- ('todo-tree "TODO tree: ")
- ('occur-tree "Occur tree: ")
- (t "Agenda command ???")))
+ (pcase type
+ (`tags "Tags: ")
+ (`todo "TODO: ")
+ (`tags-tree "Tags tree: ")
+ (`todo-tree "TODO tree: ")
+ (`occur-tree "Occur tree: ")
+ (_ "Agenda command ???")))
(defun org-mouse-list-options-menu (alloptions &optional function)
(let ((options (save-match-data
(split-string (match-string-no-properties 1)))))
(print options)
- (loop for name in alloptions
- collect
- (vector name
- `(progn
- (replace-match
- (mapconcat 'identity
- (sort (if (member ',name ',options)
- (delete ',name ',options)
- (cons ',name ',options))
- 'string-lessp)
- " ")
- nil nil nil 1)
- (when (functionp ',function) (funcall ',function)))
- :style 'toggle
- :selected (and (member name options) t)))))
+ (cl-loop for name in alloptions
+ collect
+ (vector name
+ `(progn
+ (replace-match
+ (mapconcat 'identity
+ (sort (if (member ',name ',options)
+ (delete ',name ',options)
+ (cons ',name ',options))
+ 'string-lessp)
+ " ")
+ nil nil nil 1)
+ (when (functionp ',function) (funcall ',function)))
+ :style 'toggle
+ :selected (and (member name options) t)))))
(defun org-mouse-clip-text (text maxlength)
(if (> (length text) maxlength)
@@ -498,7 +498,7 @@ SCHEDULED: or DEADLINE: or ANYTHINGLIKETHIS:"
`("Main Menu"
["Show Overview" org-mouse-show-overview t]
["Show Headlines" org-mouse-show-headlines t]
- ["Show All" show-all t]
+ ["Show All" outline-show-all t]
["Remove Highlights" org-remove-occur-highlights
:visible org-occur-highlights]
"--"
@@ -556,12 +556,12 @@ SCHEDULED: or DEADLINE: or ANYTHINGLIKETHIS:"
(let ((contextdata (assq context contextlist)))
(when contextdata
(save-excursion
- (goto-char (second contextdata))
- (re-search-forward ".*" (third contextdata))))))
+ (goto-char (nth 1 contextdata))
+ (re-search-forward ".*" (nth 2 contextdata))))))
(defun org-mouse-for-each-item (funct)
- ;; Functions called by `org-apply-on-list' need an argument
- (let ((wrap-fun (lambda (c) (funcall funct))))
+ ;; Functions called by `org-apply-on-list' need an argument.
+ (let ((wrap-fun (lambda (_) (funcall funct))))
(when (ignore-errors (goto-char (org-in-item-p)))
(save-excursion (org-apply-on-list wrap-fun nil)))))
@@ -572,14 +572,14 @@ This means, between the beginning of line and the point."
(skip-chars-backward " \t*") (bolp)))
(defun org-mouse-insert-item (text)
- (case (org-mouse-line-position)
- (:beginning ; insert before
+ (cl-case (org-mouse-line-position)
+ (:beginning ; insert before
(beginning-of-line)
(looking-at "[ \t]*")
(open-line 1)
- (org-indent-to-column (- (match-end 0) (match-beginning 0)))
+ (indent-to-column (- (match-end 0) (match-beginning 0)))
(insert "+ "))
- (:middle ; insert after
+ (:middle ; insert after
(end-of-line)
(newline t)
(indent-relative)
@@ -587,7 +587,7 @@ This means, between the beginning of line and the point."
(:end ; insert text here
(skip-chars-backward " \t")
(kill-region (point) (point-at-eol))
- (unless (org-looking-back org-mouse-punctuation)
+ (unless (looking-back org-mouse-punctuation (line-beginning-position))
(insert (concat org-mouse-punctuation " ")))))
(insert text)
(beginning-of-line))
@@ -638,14 +638,15 @@ This means, between the beginning of line and the point."
(progn (save-excursion (goto-char (region-beginning)) (insert "[["))
(save-excursion (goto-char (region-end)) (insert "]]")))]
["Insert Link Here" (org-mouse-yank-link ',event)]))))
- ((save-excursion (beginning-of-line) (looking-at "#\\+STARTUP: \\(.*\\)"))
+ ((save-excursion (beginning-of-line) (looking-at "[ \t]*#\\+STARTUP: \\(.*\\)"))
(popup-menu
`(nil
,@(org-mouse-list-options-menu (mapcar 'car org-startup-options)
'org-mode-restart))))
((or (eolp)
(and (looking-at "\\( \\|\t\\)\\(+:[0-9a-zA-Z_:]+\\)?\\( \\|\t\\)+$")
- (org-looking-back " \\|\t" (- (point) 2))))
+ (looking-back " \\|\t" (- (point) 2)
+ (line-beginning-position))))
(org-mouse-popup-global-menu))
((funcall get-context :checkbox)
(popup-menu
@@ -737,13 +738,13 @@ This means, between the beginning of line and the point."
["- 1 Month" (org-timestamp-change -1 'month)])))
((funcall get-context :table-special)
(let ((mdata (match-data)))
- (incf (car mdata) 2)
+ (cl-incf (car mdata) 2)
(store-match-data mdata))
(message "match: %S" (match-string 0))
(popup-menu `(nil ,@(org-mouse-keyword-replace-menu
'(" " "!" "^" "_" "$" "#" "*" "'") 0
(lambda (mark)
- (case (string-to-char mark)
+ (cl-case (string-to-char mark)
(? "( ) Nothing Special")
(?! "(!) Column Names")
(?^ "(^) Field Names Above")
@@ -914,7 +915,7 @@ This means, between the beginning of line and the point."
((org-footnote-at-reference-p) nil)
(t ad-do-it))))))
-(defun org-mouse-move-tree-start (event)
+(defun org-mouse-move-tree-start (_event)
(interactive "e")
(message "Same line: promote/demote, (***):move before, (text): make a child"))
@@ -993,7 +994,7 @@ This means, between the beginning of line and the point."
(defvar org-mouse-cmd) ;dynamically scoped from `org-with-remote-undo'.
(defun org-mouse-do-remotely (command)
- ; (org-agenda-check-no-diary)
+ ;; (org-agenda-check-no-diary)
(when (get-text-property (point) 'org-marker)
(let* ((anticol (- (point-at-eol) (point)))
(marker (get-text-property (point) 'org-marker))
@@ -1031,7 +1032,7 @@ This means, between the beginning of line and the point."
(org-agenda-change-all-lines newhead hdmarker 'fixface))))
t))))
-(defun org-mouse-agenda-context-menu (&optional event)
+(defun org-mouse-agenda-context-menu (&optional _event)
(or (org-mouse-do-remotely 'org-mouse-context-menu)
(popup-menu
'("Agenda"
@@ -1093,17 +1094,17 @@ This means, between the beginning of line and the point."
; (setq org-agenda-mode-hook nil)
(defvar org-agenda-mode-map)
(add-hook 'org-agenda-mode-hook
- #'(lambda ()
- (setq org-mouse-context-menu-function 'org-mouse-agenda-context-menu)
- (org-defkey org-agenda-mode-map [mouse-3] 'org-mouse-show-context-menu)
- (org-defkey org-agenda-mode-map [down-mouse-3] 'org-mouse-move-tree-start)
- (org-defkey org-agenda-mode-map [C-mouse-4] 'org-agenda-earlier)
- (org-defkey org-agenda-mode-map [C-mouse-5] 'org-agenda-later)
- (org-defkey org-agenda-mode-map [drag-mouse-3]
- #'(lambda (event) (interactive "e")
- (case (org-mouse-get-gesture event)
- (:left (org-agenda-earlier 1))
- (:right (org-agenda-later 1)))))))
+ (lambda ()
+ (setq org-mouse-context-menu-function 'org-mouse-agenda-context-menu)
+ (org-defkey org-agenda-mode-map [mouse-3] 'org-mouse-show-context-menu)
+ (org-defkey org-agenda-mode-map [down-mouse-3] 'org-mouse-move-tree-start)
+ (org-defkey org-agenda-mode-map [C-mouse-4] 'org-agenda-earlier)
+ (org-defkey org-agenda-mode-map [C-mouse-5] 'org-agenda-later)
+ (org-defkey org-agenda-mode-map [drag-mouse-3]
+ (lambda (event) (interactive "e")
+ (cl-case (org-mouse-get-gesture event)
+ (:left (org-agenda-earlier 1))
+ (:right (org-agenda-later 1)))))))
(provide 'org-mouse)
diff --git a/lisp/org/org-pcomplete.el b/lisp/org/org-pcomplete.el
index 034c20e3077..61ec5fad4c3 100644
--- a/lisp/org/org-pcomplete.el
+++ b/lisp/org/org-pcomplete.el
@@ -1,4 +1,4 @@
-;;; org-pcomplete.el --- In-buffer completion code
+;;; org-pcomplete.el --- In-buffer Completion Code -*- lexical-binding: t; -*-
;; Copyright (C) 2004-2017 Free Software Foundation, Inc.
;;
@@ -27,21 +27,17 @@
;;;; Require other packages
-(eval-when-compile
- (require 'cl))
-
(require 'org-macs)
(require 'org-compat)
(require 'pcomplete)
-(declare-function org-split-string "org" (string &optional separators))
-(declare-function org-make-org-heading-search-string "org"
- (&optional string))
+(declare-function org-make-org-heading-search-string "org" (&optional string))
(declare-function org-get-buffer-tags "org" ())
(declare-function org-get-tags "org" ())
(declare-function org-buffer-property-keys "org"
- (&optional include-specials include-defaults include-columns))
-(declare-function org-entry-properties "org" (&optional pom which specific))
+ (&optional specials defaults columns ignore-malformed))
+(declare-function org-entry-properties "org" (&optional pom which))
+(declare-function org-tag-alist-to-string "org" (alist &optional skip-key))
;;;; Customization variables
@@ -52,12 +48,13 @@
(defvar org-drawer-regexp)
(defvar org-property-re)
+(defvar org-current-tag-alist)
(defun org-thing-at-point ()
"Examine the thing at point and let the caller know what it is.
The return value is a string naming the thing at point."
(let ((beg1 (save-excursion
- (skip-chars-backward (org-re "[:alnum:]-_@"))
+ (skip-chars-backward "[:alnum:]-_@")
(point)))
(beg (save-excursion
(skip-chars-backward "a-zA-Z0-9-_:$")
@@ -93,8 +90,10 @@ The return value is a string naming the thing at point."
(skip-chars-backward "[ \t\n]")
;; org-drawer-regexp matches a whole line but while
;; looking-back, we just ignore trailing whitespaces
- (or (org-looking-back (substring org-drawer-regexp 0 -1))
- (org-looking-back org-property-re))))
+ (or (looking-back (substring org-drawer-regexp 0 -1)
+ (line-beginning-position))
+ (looking-back org-property-re
+ (line-beginning-position)))))
(cons "prop" nil))
((and (equal (char-before beg1) ?:)
(not (equal (char-after (point-at-bol)) ?*)))
@@ -140,7 +139,6 @@ When completing for #+STARTUP, for example, this function returns
pcomplete-default-completion-function))))
(defvar org-options-keywords) ; From org.el
-(defvar org-element-block-name-alist) ; From org-element.el
(defvar org-element-affiliated-keywords) ; From org-element.el
(declare-function org-get-export-keywords "org" ())
(defun pcomplete/org-mode/file-option ()
@@ -153,16 +151,19 @@ When completing for #+STARTUP, for example, this function returns
(mapcar (lambda (keyword) (concat keyword ": "))
org-element-affiliated-keywords)
(let (block-names)
- (dolist (block-info org-element-block-name-alist block-names)
- (let ((name (car block-info)))
- (push (format "END_%s" name) block-names)
- (push (concat "BEGIN_"
- name
- ;; Since language is compulsory in
- ;; source blocks, add a space.
- (and (equal name "SRC") " "))
- block-names)
- (push (format "ATTR_%s: " name) block-names))))
+ (dolist (name
+ '("CENTER" "COMMENT" "EXAMPLE" "EXPORT" "QUOTE" "SRC"
+ "VERSE")
+ block-names)
+ (push (format "END_%s" name) block-names)
+ (push (concat "BEGIN_"
+ name
+ ;; Since language is compulsory in
+ ;; export blocks source blocks, add
+ ;; a space.
+ (and (member name '("EXPORT" "SRC")) " "))
+ block-names)
+ (push (format "ATTR_%s: " name) block-names)))
(mapcar (lambda (keyword) (concat keyword ": "))
(org-get-export-keywords))))
(substring pcomplete-stub 2)))
@@ -233,20 +234,10 @@ When completing for #+STARTUP, for example, this function returns
(setq opts (delete "showstars" opts)))))
opts))))
-(defvar org-tag-alist)
(defun pcomplete/org-mode/file-option/tags ()
"Complete arguments for the #+TAGS file option."
(pcomplete-here
- (list
- (mapconcat (lambda (x)
- (cond
- ((eq :startgroup (car x)) "{")
- ((eq :endgroup (car x)) "}")
- ((eq :grouptags (car x)) ":")
- ((eq :newline (car x)) "\\n")
- ((cdr x) (format "%s(%c)" (car x) (cdr x)))
- (t (car x))))
- org-tag-alist " "))))
+ (list (org-tag-alist-to-string org-current-tag-alist))))
(defun pcomplete/org-mode/file-option/title ()
"Complete arguments for the #+TITLE file option."
@@ -271,8 +262,8 @@ When completing for #+STARTUP, for example, this function returns
"|:" "tags:" "tasks:" "<:" "todo:")
;; OPTION items from registered back-ends.
(let (items)
- (dolist (backend (org-bound-and-true-p
- org-export--registered-backends))
+ (dolist (backend (bound-and-true-p
+ org-export-registered-backends))
(dolist (option (org-export-backend-options backend))
(let ((item (nth 2 option)))
(when item (push (concat item ":") items)))))
@@ -283,7 +274,7 @@ When completing for #+STARTUP, for example, this function returns
(while (pcomplete-here
(pcomplete-uniqify-list
(mapcar (lambda (item) (format "%s:" (car item)))
- (org-bound-and-true-p org-html-infojs-opts-table))))))
+ (bound-and-true-p org-html-infojs-opts-table))))))
(defun pcomplete/org-mode/file-option/bind ()
"Complete arguments for the #+BIND file option, which are variable names."
@@ -324,26 +315,24 @@ This needs more work, to handle headings with lots of spaces in them."
(save-excursion
(goto-char (point-min))
(let (tbl)
- (while (re-search-forward org-todo-line-regexp nil t)
- (push (org-make-org-heading-search-string
- (match-string-no-properties 3))
- tbl))
+ (let ((case-fold-search nil))
+ (while (re-search-forward org-todo-line-regexp nil t)
+ (push (org-make-org-heading-search-string
+ (match-string-no-properties 3))
+ tbl)))
(pcomplete-uniqify-list tbl)))
(substring pcomplete-stub 1))))
-(defvar org-tag-alist)
(defun pcomplete/org-mode/tag ()
"Complete a tag name. Omit tags already set."
(while (pcomplete-here
- (mapcar (lambda (x)
- (concat x ":"))
+ (mapcar (lambda (x) (concat x ":"))
(let ((lst (pcomplete-uniqify-list
- (or (remove
+ (or (remq
nil
- (mapcar (lambda (x)
- (and (stringp (car x)) (car x)))
- org-tag-alist))
- (mapcar 'car (org-get-buffer-tags))))))
+ (mapcar (lambda (x) (org-string-nw-p (car x)))
+ org-current-tag-alist))
+ (mapcar #'car (org-get-buffer-tags))))))
(dolist (tag (org-get-tags))
(setq lst (delete tag lst)))
lst))
@@ -357,31 +346,12 @@ This needs more work, to handle headings with lots of spaces in them."
(concat x ": "))
(let ((lst (pcomplete-uniqify-list
(copy-sequence
- (org-buffer-property-keys nil t t)))))
+ (org-buffer-property-keys nil t t t)))))
(dolist (prop (org-entry-properties))
(setq lst (delete (car prop) lst)))
lst))
(substring pcomplete-stub 1)))
-(defvar org-drawers)
-
-(defun pcomplete/org-mode/drawer ()
- "Complete a drawer name."
- (let ((spc (save-excursion
- (move-beginning-of-line 1)
- (looking-at "^\\([ \t]*\\):")
- (match-string 1)))
- (cpllist (mapcar (lambda (x) (concat x ": ")) org-drawers)))
- (pcomplete-here cpllist
- (substring pcomplete-stub 1)
- (unless (or (not (delq
- nil
- (mapcar (lambda(x)
- (string-match (substring pcomplete-stub 1) x))
- cpllist)))
- (looking-at "[ \t]*\n.*:END:"))
- (save-excursion (insert "\n" spc ":END:"))))))
-
(defun pcomplete/org-mode/block-option/src ()
"Complete the arguments of a begin_src block.
Complete a language in the first field, the header arguments and switches."
diff --git a/lisp/org/org-plot.el b/lisp/org/org-plot.el
index 5ccfbb1e662..449143a47af 100644
--- a/lisp/org/org-plot.el
+++ b/lisp/org/org-plot.el
@@ -1,4 +1,4 @@
-;;; org-plot.el --- Support for plotting from Org-mode
+;;; org-plot.el --- Support for Plotting from Org -*- lexical-binding: t; -*-
;; Copyright (C) 2008-2017 Free Software Foundation, Inc.
;;
@@ -25,14 +25,14 @@
;; Borrows ideas and a couple of lines of code from org-exp.el.
-;; Thanks to the org-mode mailing list for testing and implementation
-;; and feature suggestions
+;; Thanks to the Org mailing list for testing and implementation and
+;; feature suggestions
;;; Code:
+
+(require 'cl-lib)
(require 'org)
(require 'org-table)
-(eval-when-compile
- (require 'cl))
(declare-function gnuplot-delchar-or-maybe-eof "ext:gnuplot" (arg))
(declare-function gnuplot-mode "ext:gnuplot" ())
@@ -49,41 +49,39 @@
(defun org-plot/add-options-to-plist (p options)
"Parse an OPTIONS line and set values in the property list P.
Returns the resulting property list."
- (let (o)
- (when options
- (let ((op '(("type" . :plot-type)
- ("script" . :script)
- ("line" . :line)
- ("set" . :set)
- ("title" . :title)
- ("ind" . :ind)
- ("deps" . :deps)
- ("with" . :with)
- ("file" . :file)
- ("labels" . :labels)
- ("map" . :map)
- ("timeind" . :timeind)
- ("timefmt" . :timefmt)))
- (multiples '("set" "line"))
- (regexp ":\\([\"][^\"]+?[\"]\\|[(][^)]+?[)]\\|[^ \t\n\r;,.]*\\)")
- (start 0)
- o)
- (while (setq o (pop op))
- (if (member (car o) multiples) ;; keys with multiple values
- (while (string-match
- (concat (regexp-quote (car o)) regexp)
- options start)
- (setq start (match-end 0))
- (setq p (plist-put p (cdr o)
- (cons (car (read-from-string
- (match-string 1 options)))
- (plist-get p (cdr o)))))
- p)
- (if (string-match (concat (regexp-quote (car o)) regexp)
- options)
- (setq p (plist-put p (cdr o)
- (car (read-from-string
- (match-string 1 options)))))))))))
+ (when options
+ (let ((op '(("type" . :plot-type)
+ ("script" . :script)
+ ("line" . :line)
+ ("set" . :set)
+ ("title" . :title)
+ ("ind" . :ind)
+ ("deps" . :deps)
+ ("with" . :with)
+ ("file" . :file)
+ ("labels" . :labels)
+ ("map" . :map)
+ ("timeind" . :timeind)
+ ("timefmt" . :timefmt)))
+ (multiples '("set" "line"))
+ (regexp ":\\([\"][^\"]+?[\"]\\|[(][^)]+?[)]\\|[^ \t\n\r;,.]*\\)")
+ (start 0))
+ (dolist (o op)
+ (if (member (car o) multiples) ;; keys with multiple values
+ (while (string-match
+ (concat (regexp-quote (car o)) regexp)
+ options start)
+ (setq start (match-end 0))
+ (setq p (plist-put p (cdr o)
+ (cons (car (read-from-string
+ (match-string 1 options)))
+ (plist-get p (cdr o)))))
+ p)
+ (if (string-match (concat (regexp-quote (car o)) regexp)
+ options)
+ (setq p (plist-put p (cdr o)
+ (car (read-from-string
+ (match-string 1 options))))))))))
p)
(defun org-plot/goto-nearest-table ()
@@ -119,10 +117,9 @@ will be added. Returns the resulting property list."
Pass PARAMS through to `orgtbl-to-generic' when exporting TABLE."
(with-temp-file
data-file
- (make-local-variable 'org-plot-timestamp-fmt)
- (setq org-plot-timestamp-fmt (or
- (plist-get params :timefmt)
- "%Y-%m-%d-%H:%M:%S"))
+ (setq-local org-plot-timestamp-fmt (or
+ (plist-get params :timefmt)
+ "%Y-%m-%d-%H:%M:%S"))
(insert (orgtbl-to-generic
table
(org-combine-plists
@@ -140,7 +137,7 @@ and dependant variables."
(deps (if (plist-member params :deps)
(mapcar (lambda (val) (- val 1)) (plist-get params :deps))
(let (collector)
- (dotimes (col (length (first table)))
+ (dotimes (col (length (nth 0 table)))
(setf collector (cons col collector)))
collector)))
(counter 0)
@@ -158,7 +155,7 @@ and dependant variables."
table)))
;; write table to gnuplot grid datafile format
(with-temp-file data-file
- (let ((num-rows (length table)) (num-cols (length (first table)))
+ (let ((num-rows (length table)) (num-cols (length (nth 0 table)))
(gnuplot-row (lambda (col row value)
(setf col (+ 1 col)) (setf row (+ 1 row))
(format "%f %f %f\n%f %f %f\n"
@@ -187,9 +184,7 @@ NUM-COLS controls the number of columns plotted in a 2-d plot.
Optional argument PREFACE returns only option parameters in a
manner suitable for prepending to a user-specified script."
(let* ((type (plist-get params :plot-type))
- (with (if (equal type 'grid)
- 'pm3d
- (plist-get params :with)))
+ (with (if (eq type 'grid) 'pm3d (plist-get params :with)))
(sets (plist-get params :set))
(lines (plist-get params :line))
(map (plist-get params :map))
@@ -204,68 +199,72 @@ manner suitable for prepending to a user-specified script."
(x-labels (plist-get params :xlabels))
(y-labels (plist-get params :ylabels))
(plot-str "'%s' using %s%d%s with %s title '%s'")
- (plot-cmd (case type
- ('2d "plot")
- ('3d "splot")
- ('grid "splot")))
+ (plot-cmd (pcase type
+ (`2d "plot")
+ (`3d "splot")
+ (`grid "splot")))
(script "reset")
- ; ats = add-to-script
- (ats (lambda (line) (setf script (format "%s\n%s" script line))))
+ ;; ats = add-to-script
+ (ats (lambda (line) (setf script (concat script "\n" line))))
plot-lines)
- (when file ;; output file
+ (when file ; output file
(funcall ats (format "set term %s" (file-name-extension file)))
(funcall ats (format "set output '%s'" file)))
- (case type ;; type
- ('2d ())
- ('3d (if map (funcall ats "set map")))
- ('grid (if map (funcall ats "set pm3d map")
- (funcall ats "set pm3d"))))
- (when title (funcall ats (format "set title '%s'" title))) ;; title
- (when lines (mapc (lambda (el) (funcall ats el)) lines)) ;; line
- (when sets ;; set
- (mapc (lambda (el) (funcall ats (format "set %s" el))) sets))
- (when x-labels ;; x labels (xtics)
+ (pcase type ; type
+ (`2d ())
+ (`3d (when map (funcall ats "set map")))
+ (`grid (funcall ats (if map "set pm3d map" "set pm3d"))))
+ (when title (funcall ats (format "set title '%s'" title))) ; title
+ (mapc ats lines) ; line
+ (dolist (el sets) (funcall ats (format "set %s" el))) ; set
+ ;; Unless specified otherwise, values are TAB separated.
+ (unless (string-match-p "^set datafile separator" script)
+ (funcall ats "set datafile separator \"\\t\""))
+ (when x-labels ; x labels (xtics)
(funcall ats
(format "set xtics (%s)"
(mapconcat (lambda (pair)
(format "\"%s\" %d" (cdr pair) (car pair)))
x-labels ", "))))
- (when y-labels ;; y labels (ytics)
+ (when y-labels ; y labels (ytics)
(funcall ats
(format "set ytics (%s)"
(mapconcat (lambda (pair)
(format "\"%s\" %d" (cdr pair) (car pair)))
y-labels ", "))))
- (when time-ind ;; timestamp index
+ (when time-ind ; timestamp index
(funcall ats "set xdata time")
(funcall ats (concat "set timefmt \""
- (or timefmt ;; timefmt passed to gnuplot
+ (or timefmt ; timefmt passed to gnuplot
"%Y-%m-%d-%H:%M:%S") "\"")))
(unless preface
- (case type ;; plot command
- ('2d (dotimes (col num-cols)
- (unless (and (equal type '2d)
- (or (and ind (equal (+ 1 col) ind))
- (and deps (not (member (+ 1 col) deps)))))
+ (pcase type ; plot command
+ (`2d (dotimes (col num-cols)
+ (unless (and (eq type '2d)
+ (or (and ind (equal (1+ col) ind))
+ (and deps (not (member (1+ col) deps)))))
(setf plot-lines
(cons
(format plot-str data-file
(or (and ind (> ind 0)
- (not text-ind)
- (format "%d:" ind)) "")
- (+ 1 col)
+ (not text-ind)
+ (format "%d:" ind)) "")
+ (1+ col)
(if text-ind (format ":xticlabel(%d)" ind) "")
with
- (or (nth col col-labels) (format "%d" (+ 1 col))))
+ (or (nth col col-labels)
+ (format "%d" (1+ col))))
plot-lines)))))
- ('3d
+ (`3d
(setq plot-lines (list (format "'%s' matrix with %s title ''"
data-file with))))
- ('grid
+ (`grid
(setq plot-lines (list (format "'%s' with %s title ''"
data-file with)))))
(funcall ats
- (concat plot-cmd " " (mapconcat 'identity (reverse plot-lines) ",\\\n "))))
+ (concat plot-cmd " " (mapconcat #'identity
+ (reverse plot-lines)
+ ",\\\n "))))
script))
;;-----------------------------------------------------------------------------
@@ -279,59 +278,59 @@ line directly before or after the table."
(require 'gnuplot)
(save-window-excursion
(delete-other-windows)
- (when (get-buffer "*gnuplot*") ;; reset *gnuplot* if it already running
+ (when (get-buffer "*gnuplot*") ; reset *gnuplot* if it already running
(with-current-buffer "*gnuplot*"
- (goto-char (point-max))
- (gnuplot-delchar-or-maybe-eof nil)))
+ (goto-char (point-max))))
(org-plot/goto-nearest-table)
- ;; set default options
- (mapc
- (lambda (pair)
- (unless (plist-member params (car pair))
- (setf params (plist-put params (car pair) (cdr pair)))))
- org-plot/gnuplot-default-options)
+ ;; Set default options.
+ (dolist (pair org-plot/gnuplot-default-options)
+ (unless (plist-member params (car pair))
+ (setf params (plist-put params (car pair) (cdr pair)))))
;; collect table and table information
(let* ((data-file (make-temp-file "org-plot"))
(table (org-table-to-lisp))
- (num-cols (length (if (eq (first table) 'hline) (second table)
- (first table)))))
- (while (equal 'hline (first table)) (setf table (cdr table)))
- (when (equal (second table) 'hline)
- (setf params (plist-put params :labels (first table))) ;; headers to labels
- (setf table (delq 'hline (cdr table)))) ;; clean non-data from table
- ;; collect options
+ (num-cols (length (if (eq (nth 0 table) 'hline) (nth 1 table)
+ (nth 0 table)))))
+ (run-with-idle-timer 0.1 nil #'delete-file data-file)
+ (while (eq 'hline (car table)) (setf table (cdr table)))
+ (when (eq (cadr table) 'hline)
+ (setf params
+ (plist-put params :labels (nth 0 table))) ; headers to labels
+ (setf table (delq 'hline (cdr table)))) ; clean non-data from table
+ ;; Collect options.
(save-excursion (while (and (equal 0 (forward-line -1))
(looking-at "[[:space:]]*#\\+"))
(setf params (org-plot/collect-options params))))
- ;; dump table to datafile (very different for grid)
- (case (plist-get params :plot-type)
- ('2d (org-plot/gnuplot-to-data table data-file params))
- ('3d (org-plot/gnuplot-to-data table data-file params))
- ('grid (let ((y-labels (org-plot/gnuplot-to-grid-data
+ ;; Dump table to datafile (very different for grid).
+ (pcase (plist-get params :plot-type)
+ (`2d (org-plot/gnuplot-to-data table data-file params))
+ (`3d (org-plot/gnuplot-to-data table data-file params))
+ (`grid (let ((y-labels (org-plot/gnuplot-to-grid-data
table data-file params)))
(when y-labels (plist-put params :ylabels y-labels)))))
- ;; check for timestamp ind column
- (let ((ind (- (plist-get params :ind) 1)))
- (when (and (>= ind 0) (equal '2d (plist-get params :plot-type)))
+ ;; Check for timestamp ind column.
+ (let ((ind (1- (plist-get params :ind))))
+ (when (and (>= ind 0) (eq '2d (plist-get params :plot-type)))
(if (= (length
(delq 0 (mapcar
(lambda (el)
- (if (string-match org-ts-regexp3 el)
- 0 1))
- (mapcar (lambda (row) (nth ind row)) table)))) 0)
+ (if (string-match org-ts-regexp3 el) 0 1))
+ (mapcar (lambda (row) (nth ind row)) table))))
+ 0)
(plist-put params :timeind t)
- ;; check for text ind column
+ ;; Check for text ind column.
(if (or (string= (plist-get params :with) "hist")
(> (length
(delq 0 (mapcar
(lambda (el)
(if (string-match org-table-number-regexp el)
0 1))
- (mapcar (lambda (row) (nth ind row)) table)))) 0))
+ (mapcar (lambda (row) (nth ind row)) table))))
+ 0))
(plist-put params :textind t)))))
- ;; write script
+ ;; Write script.
(with-temp-buffer
- (if (plist-get params :script) ;; user script
+ (if (plist-get params :script) ; user script
(progn (insert
(org-plot/gnuplot-script data-file num-cols params t))
(insert "\n")
@@ -339,14 +338,12 @@ line directly before or after the table."
(goto-char (point-min))
(while (re-search-forward "$datafile" nil t)
(replace-match data-file nil nil)))
- (insert
- (org-plot/gnuplot-script data-file num-cols params)))
- ;; graph table
+ (insert (org-plot/gnuplot-script data-file num-cols params)))
+ ;; Graph table.
(gnuplot-mode)
(gnuplot-send-buffer-to-gnuplot))
- ;; cleanup
- (bury-buffer (get-buffer "*gnuplot*"))
- (run-with-idle-timer 0.1 nil (lambda () (delete-file data-file))))))
+ ;; Cleanup.
+ (bury-buffer (get-buffer "*gnuplot*")))))
(provide 'org-plot)
diff --git a/lisp/org/org-protocol.el b/lisp/org/org-protocol.el
index 4bd83bea486..82543567456 100644
--- a/lisp/org/org-protocol.el
+++ b/lisp/org/org-protocol.el
@@ -1,4 +1,4 @@
-;;; org-protocol.el --- Intercept calls from emacsclient to trigger custom actions.
+;;; org-protocol.el --- Intercept Calls from Emacsclient to Trigger Custom Actions -*- lexical-binding: t; -*-
;;
;; Copyright (C) 2008-2017 Free Software Foundation, Inc.
;;
@@ -49,7 +49,7 @@
;; 4.) Try this from the command line (adjust the URL as needed):
;;
;; $ emacsclient \
-;; org-protocol://store-link://http:%2F%2Flocalhost%2Findex.html/The%20title
+;; org-protocol://store-link?url=http:%2F%2Flocalhost%2Findex.html&title=The%20title
;;
;; 5.) Optionally add custom sub-protocols and handlers:
;;
@@ -60,7 +60,7 @@
;;
;; A "sub-protocol" will be found in URLs like this:
;;
-;; org-protocol://sub-protocol://data
+;; org-protocol://sub-protocol?key=val&key2=val2
;;
;; If it works, you can now setup other applications for using this feature.
;;
@@ -81,12 +81,12 @@
;; * `org-protocol-open-source' uses the sub-protocol \"open-source\" and maps
;; URLs to local filenames defined in `org-protocol-project-alist'.
;;
-;; * `org-protocol-store-link' stores an Org-link (if Org-mode is present) and
+;; * `org-protocol-store-link' stores an Org link (if Org is present) and
;; pushes the browsers URL to the `kill-ring' for yanking. This handler is
;; triggered through the sub-protocol \"store-link\".
;;
;; * Call `org-protocol-capture' by using the sub-protocol \"capture\". If
-;; Org-mode is loaded, Emacs will pop-up a capture buffer and fill the
+;; Org is loaded, Emacs will pop-up a capture buffer and fill the
;; template with the data provided. I.e. the browser's URL is inserted as an
;; Org-link of which the page title will be the description part. If text
;; was select in the browser, that text will be the body of the entry.
@@ -94,20 +94,20 @@
;; You may use the same bookmark URL for all those standard handlers and just
;; adjust the sub-protocol used:
;;
-;; location.href='org-protocol://sub-protocol://'+
-;; encodeURIComponent(location.href)+'/'+
-;; encodeURIComponent(document.title)+'/'+
+;; location.href='org-protocol://sub-protocol?url='+
+;; encodeURIComponent(location.href)+'&title='+
+;; encodeURIComponent(document.title)+'&body='+
;; encodeURIComponent(window.getSelection())
;;
;; The handler for the sub-protocol \"capture\" detects an optional template
;; char that, if present, triggers the use of a special template.
;; Example:
;;
-;; location.href='org-protocol://sub-protocol://x/'+ ...
+;; location.href='org-protocol://capture?template=x'+ ...
;;
-;; use template ?x.
+;; uses template ?x.
;;
-;; Note, that using double slashes is optional from org-protocol.el's point of
+;; Note that using double slashes is optional from org-protocol.el's point of
;; view because emacsclient squashes the slashes to one.
;;
;;
@@ -116,25 +116,12 @@
;;; Code:
(require 'org)
-(eval-when-compile
- (require 'cl))
(declare-function org-publish-get-project-from-filename "ox-publish"
(filename &optional up))
(declare-function server-edit "server" (&optional arg))
-(define-obsolete-function-alias
- 'org-protocol-unhex-compound 'org-link-unescape-compound
- "2011-02-17")
-
-(define-obsolete-function-alias
- 'org-protocol-unhex-string 'org-link-unescape
- "2011-02-17")
-
-(define-obsolete-function-alias
- 'org-protocol-unhex-single-byte-sequence
- 'org-link-unescape-single-byte-sequence
- "2011-02-17")
+(defvar org-capture-link-is-already-stored)
(defgroup org-protocol nil
"Intercept calls from emacsclient to trigger custom actions.
@@ -225,27 +212,36 @@ Each element of this list must be of the form:
(module-name :protocol protocol :function func :kill-client nil)
-protocol - protocol to detect in a filename without trailing colon and slashes.
- See rfc1738 section 2.1 for more on this.
- If you define a protocol \"my-protocol\", `org-protocol-check-filename-for-protocol'
- will search filenames for \"org-protocol:/my-protocol:/\"
- and trigger your action for every match. `org-protocol' is defined in
- `org-protocol-the-protocol'. Double and triple slashes are compressed
- to one by emacsclient.
-
-function - function that handles requests with protocol and takes exactly one
- argument: the filename with all protocols stripped. If the function
- returns nil, emacsclient and -server do nothing. Any non-nil return
- value is considered a valid filename and thus passed to the server.
-
- `org-protocol.el provides some support for handling those filenames,
- if you stay with the conventions used for the standard handlers in
- `org-protocol-protocol-alist-default'. See `org-protocol-split-data'.
+protocol - protocol to detect in a filename without trailing
+ colon and slashes. See rfc1738 section 2.1 for more
+ on this. If you define a protocol \"my-protocol\",
+ `org-protocol-check-filename-for-protocol' will search
+ filenames for \"org-protocol:/my-protocol\" and
+ trigger your action for every match. `org-protocol'
+ is defined in `org-protocol-the-protocol'. Double and
+ triple slashes are compressed to one by emacsclient.
+
+function - function that handles requests with protocol and takes
+ one argument. If a new-style link (key=val&key2=val2)
+ is given, the argument will be a property list with
+ the values from the link. If an old-style link is
+ given (val1/val2), the argument will be the filename
+ with all protocols stripped.
+
+ If the function returns nil, emacsclient and -server
+ do nothing. Any non-nil return value is considered a
+ valid filename and thus passed to the server.
+
+ `org-protocol.el' provides some support for handling
+ old-style filenames, if you follow the conventions
+ used for the standard handlers in
+ `org-protocol-protocol-alist-default'. See
+ `org-protocol-parse-parameters'.
kill-client - If t, kill the client immediately, once the sub-protocol is
detected. This is necessary for actions that can be interrupted by
- `C-g' to avoid dangling emacsclients. Note, that all other command
- line arguments but the this one will be discarded, greedy handlers
+ `C-g' to avoid dangling emacsclients. Note that all other command
+ line arguments but the this one will be discarded. Greedy handlers
still receive the whole list of arguments though.
Here is an example:
@@ -269,7 +265,7 @@ string with two characters."
(defcustom org-protocol-data-separator "/+\\|\\?"
"The default data separator to use.
- This should be a single regexp string."
+This should be a single regexp string."
:group 'org-protocol
:version "24.4"
:package-version '(Org . "8.0")
@@ -278,21 +274,20 @@ string with two characters."
;;; Helper functions:
(defun org-protocol-sanitize-uri (uri)
- "emacsclient compresses double and triple slashes.
-Slashes are sanitized to double slashes here."
+ "Sanitize slashes to double-slashes in URI.
+Emacsclient compresses double and triple slashes."
(when (string-match "^\\([a-z]+\\):/" uri)
(let* ((splitparts (split-string uri "/+")))
(setq uri (concat (car splitparts) "//" (mapconcat 'identity (cdr splitparts) "/")))))
uri)
(defun org-protocol-split-data (data &optional unhexify separator)
- "Split what an org-protocol handler function gets as only argument.
-DATA is that one argument. DATA is split at each occurrence of
-SEPARATOR (regexp). If no SEPARATOR is specified or SEPARATOR is
-nil, assume \"/+\". The results of that splitting are returned
-as a list. If UNHEXIFY is non-nil, hex-decode each split part.
-If UNHEXIFY is a function, use that function to decode each split
-part."
+ "Split the DATA argument for an org-protocol handler function.
+If UNHEXIFY is non-nil, hex-decode each split part. If UNHEXIFY
+is a function, use that function to decode each split part. The
+string is split at each occurrence of SEPARATOR (regexp). If no
+SEPARATOR is specified or SEPARATOR is nil, assume \"/+\". The
+results of that splitting are returned as a list."
(let* ((sep (or separator "/+\\|\\?"))
(split-parts (split-string data sep)))
(if unhexify
@@ -302,23 +297,25 @@ part."
split-parts)))
(defun org-protocol-flatten-greedy (param-list &optional strip-path replacement)
- "Greedy handlers might receive a list like this from emacsclient:
- ((\"/dir/org-protocol:/greedy:/~/path1\" (23 . 12)) (\"/dir/param\"))
-where \"/dir/\" is the absolute path to emacsclients working directory. This
+ "Transform PARAM-LIST into a flat list for greedy handlers.
+
+Greedy handlers might receive a list like this from emacsclient:
+\((\"/dir/org-protocol:/greedy:/~/path1\" (23 . 12)) (\"/dir/param\"))
+where \"/dir/\" is the absolute path to emacsclient's working directory. This
function transforms it into a flat list using `org-protocol-flatten' and
transforms the elements of that list as follows:
-If strip-path is non-nil, remove the \"/dir/\" prefix from all members of
+If STRIP-PATH is non-nil, remove the \"/dir/\" prefix from all members of
param-list.
-If replacement is string, replace the \"/dir/\" prefix with it.
+If REPLACEMENT is string, replace the \"/dir/\" prefix with it.
The first parameter, the one that contains the protocols, is always changed.
Everything up to the end of the protocols is stripped.
Note, that this function will always behave as if
`org-protocol-reverse-list-of-files' was set to t and the returned list will
-reflect that. I.e. emacsclients first parameter will be the first one in the
+reflect that. emacsclient's first parameter will be the first one in the
returned list."
(let* ((l (org-protocol-flatten (if org-protocol-reverse-list-of-files
param-list
@@ -345,50 +342,106 @@ returned list."
ret)
l)))
-(defun org-protocol-flatten (l)
- "Greedy handlers might receive a list like this from emacsclient:
- ((\"/dir/org-protocol:/greedy:/~/path1\" (23 . 12)) (\"/dir/param\"))
+(defun org-protocol-flatten (list)
+ "Transform LIST into a flat list.
+
+Greedy handlers might receive a list like this from emacsclient:
+\((\"/dir/org-protocol:/greedy:/~/path1\" (23 . 12)) (\"/dir/param\"))
where \"/dir/\" is the absolute path to emacsclients working directory.
This function transforms it into a flat list."
- (if (null l) ()
- (if (listp l)
- (append (org-protocol-flatten (car l)) (org-protocol-flatten (cdr l)))
- (list l))))
-
+ (if (null list) ()
+ (if (listp list)
+ (append (org-protocol-flatten (car list)) (org-protocol-flatten (cdr list)))
+ (list list))))
+
+(defun org-protocol-parse-parameters (info &optional new-style default-order)
+ "Return a property list of parameters from INFO.
+If NEW-STYLE is non-nil, treat INFO as a query string (ex:
+url=URL&title=TITLE). If old-style links are used (ex:
+org-protocol://store-link/url/title), assign them to attributes
+following DEFAULT-ORDER.
+
+If no DEFAULT-ORDER is specified, return the list of values.
+
+If INFO is already a property list, return it unchanged."
+ (if (listp info)
+ info
+ (if new-style
+ (let ((data (org-protocol-convert-query-to-plist info))
+ result)
+ (while data
+ (setq result
+ (append
+ result
+ (list
+ (pop data)
+ (org-link-unescape (pop data))))))
+ result)
+ (let ((data (org-protocol-split-data info t org-protocol-data-separator)))
+ (if default-order
+ (org-protocol-assign-parameters data default-order)
+ data)))))
+
+(defun org-protocol-assign-parameters (data default-order)
+ "Return a property list of parameters from DATA.
+Key names are taken from DEFAULT-ORDER, which should be a list of
+symbols. If DEFAULT-ORDER is shorter than the number of values
+specified, the rest of the values are treated as :key value pairs."
+ (let (result)
+ (while default-order
+ (setq result
+ (append result
+ (list (pop default-order)
+ (pop data)))))
+ (while data
+ (setq result
+ (append result
+ (list (intern (concat ":" (pop data)))
+ (pop data)))))
+ result))
;;; Standard protocol handlers:
(defun org-protocol-store-link (fname)
- "Process an org-protocol://store-link:// style url.
+ "Process an org-protocol://store-link style url.
Additionally store a browser URL as an org link. Also pushes the
link's URL to the `kill-ring'.
+Parameters: url, title (optional), body (optional)
+
+Old-style links such as org-protocol://store-link://URL/TITLE are
+also recognized.
+
The location for a browser's bookmark has to look like this:
- javascript:location.href=\\='org-protocol://store-link://\\='+ \\
- encodeURIComponent(location.href)
- encodeURIComponent(document.title)+\\='/\\='+ \\
+ javascript:location.href = \\
+ \\='org-protocol://store-link?url=\\=' + \\
+ encodeURIComponent(location.href) + \\='&title=\\=' + \\
+ encodeURIComponent(document.title);
-Don't use `escape()'! Use `encodeURIComponent()' instead. The title of the page
-could contain slashes and the location definitely will.
+Don't use `escape()'! Use `encodeURIComponent()' instead. The
+title of the page could contain slashes and the location
+definitely will.
The sub-protocol used to reach this function is set in
-`org-protocol-protocol-alist'."
- (let* ((splitparts (org-protocol-split-data fname t org-protocol-data-separator))
- (uri (org-protocol-sanitize-uri (car splitparts)))
- (title (cadr splitparts))
- orglink)
- (if (boundp 'org-stored-links)
- (setq org-stored-links (cons (list uri title) org-stored-links)))
+`org-protocol-protocol-alist'.
+
+FNAME should be a property list. If not, an old-style link of the
+form URL/TITLE can also be used."
+ (let* ((splitparts (org-protocol-parse-parameters fname nil '(:url :title)))
+ (uri (org-protocol-sanitize-uri (plist-get splitparts :url)))
+ (title (plist-get splitparts :title)))
+ (when (boundp 'org-stored-links)
+ (push (list uri title) org-stored-links))
(kill-new uri)
(message "`%s' to insert new org-link, `%s' to insert `%s'"
- (substitute-command-keys"\\[org-insert-link]")
- (substitute-command-keys"\\[yank]")
+ (substitute-command-keys "`\\[org-insert-link]'")
+ (substitute-command-keys "`\\[yank]'")
uri))
nil)
(defun org-protocol-capture (info)
- "Process an org-protocol://capture:// style url.
+ "Process an org-protocol://capture style url with INFO.
The sub-protocol used to reach this function is set in
`org-protocol-protocol-alist'.
@@ -396,16 +449,16 @@ The sub-protocol used to reach this function is set in
This function detects an URL, title and optional text, separated
by `/'. The location for a browser's bookmark looks like this:
- javascript:location.href=\\='org-protocol://capture://\\='+ \\
- encodeURIComponent(location.href)+\\='/\\=' \\
- encodeURIComponent(document.title)+\\='/\\='+ \\
+ javascript:location.href = \\='org-protocol://capture?url=\\='+ \\
+ encodeURIComponent(location.href) + \\='&title=\\=' \\
+ encodeURIComponent(document.title) + \\='&body=\\=' + \\
encodeURIComponent(window.getSelection())
By default, it uses the character `org-protocol-default-template-key',
which should be associated with a template in `org-capture-templates'.
-But you may prepend the encoded URL with a character and a slash like so:
+You may specify the template with a template= query parameter, like this:
- javascript:location.href=\\='org-protocol://capture://b/\\='+ ...
+ javascript:location.href = \\='org-protocol://capture?template=b\\='+ ...
Now template ?b will be used."
(if (and (boundp 'org-stored-links)
@@ -414,7 +467,7 @@ Now template ?b will be used."
nil)
(defun org-protocol-convert-query-to-plist (query)
- "Convert query string that is part of url to property list."
+ "Convert QUERY key=value pairs in the URL to a property list."
(if query
(apply 'append (mapcar (lambda (x)
(let ((c (split-string x "=")))
@@ -422,45 +475,52 @@ Now template ?b will be used."
(split-string query "&")))))
(defun org-protocol-do-capture (info)
- "Support `org-capture'."
- (let* ((parts (org-protocol-split-data info t org-protocol-data-separator))
- (template (or (and (>= 2 (length (car parts))) (pop parts))
+ "Perform the actual capture based on INFO."
+ (let* ((temp-parts (org-protocol-parse-parameters info))
+ (parts
+ (cond
+ ((and (listp info) (symbolp (car info))) info)
+ ((= (length (car temp-parts)) 1) ;; First parameter is exactly one character long
+ (org-protocol-assign-parameters temp-parts '(:template :url :title :body)))
+ (t
+ (org-protocol-assign-parameters temp-parts '(:url :title :body)))))
+ (template (or (plist-get parts :template)
org-protocol-default-template-key))
- (url (org-protocol-sanitize-uri (car parts)))
- (type (if (string-match "^\\([a-z]+\\):" url)
- (match-string 1 url)))
- (title (or (cadr parts) ""))
- (region (or (caddr parts) ""))
- (orglink (org-make-link-string
- url (if (string-match "[^[:space:]]" title) title url)))
- (query (or (org-protocol-convert-query-to-plist (cadddr parts)) ""))
+ (url (and (plist-get parts :url) (org-protocol-sanitize-uri (plist-get parts :url))))
+ (type (and url (if (string-match "^\\([a-z]+\\):" url)
+ (match-string 1 url))))
+ (title (or (plist-get parts :title) ""))
+ (region (or (plist-get parts :body) ""))
+ (orglink (if url
+ (org-make-link-string
+ url (if (string-match "[^[:space:]]" title) title url))
+ title))
(org-capture-link-is-already-stored t)) ;; avoid call to org-store-link
(setq org-stored-links
(cons (list url title) org-stored-links))
- (kill-new orglink)
(org-store-link-props :type type
:link url
:description title
:annotation orglink
:initial region
- :query query)
+ :query parts)
(raise-frame)
(funcall 'org-capture nil template)))
(defun org-protocol-open-source (fname)
- "Process an org-protocol://open-source:// style url.
+ "Process an org-protocol://open-source?url= style URL with FNAME.
Change a filename by mapping URLs to local filenames as set
in `org-protocol-project-alist'.
The location for a browser's bookmark should look like this:
- javascript:location.href=\\='org-protocol://open-source://\\='+ \\
+ javascript:location.href = \\='org-protocol://open-source?url=\\=' + \\
encodeURIComponent(location.href)"
;; As we enter this function for a match on our protocol, the return value
;; defaults to nil.
(let ((result nil)
- (f (org-link-unescape fname)))
+ (f (plist-get (org-protocol-parse-parameters fname nil '(:url)) :url)))
(catch 'result
(dolist (prolist org-protocol-project-alist)
(let* ((base-url (plist-get (cdr prolist) :base-url))
@@ -490,13 +550,12 @@ The location for a browser's bookmark should look like this:
(let ((rewrites (plist-get (cdr prolist) :rewrites)))
(when rewrites
(message "Rewrites found: %S" rewrites)
- (mapc
- (lambda (rewrite)
- "Try to match a rewritten URL and map it to a real file."
- ;; Compare redirects without suffix:
- (if (string-match (car rewrite) f2)
- (throw 'result (concat wdir (cdr rewrite)))))
- rewrites))))
+ (dolist (rewrite rewrites)
+ ;; Try to match a rewritten URL and map it to
+ ;; a real file. Compare redirects without
+ ;; suffix.
+ (when (string-match-p (car rewrite) f2)
+ (throw 'result (concat wdir (cdr rewrite))))))))
;; -- end of redirects --
(if (file-readable-p the-file)
@@ -509,44 +568,63 @@ The location for a browser's bookmark should look like this:
;;; Core functions:
-(defun org-protocol-check-filename-for-protocol (fname restoffiles client)
- "Detect if `org-protocol-the-protocol' and a known sub-protocol is used in fname.
+(defun org-protocol-check-filename-for-protocol (fname restoffiles _client)
+ "Check if `org-protocol-the-protocol' and a valid protocol are used in FNAME.
Sub-protocols are registered in `org-protocol-protocol-alist' and
-`org-protocol-protocol-alist-default'.
-This is, how the matching is done:
+`org-protocol-protocol-alist-default'. This is how the matching is done:
- (string-match \"protocol:/+sub-protocol:/+\" ...)
+ (string-match \"protocol:/+sub-protocol\\\\(://\\\\|\\\\?\\\\)\" ...)
protocol and sub-protocol are regexp-quoted.
-If a matching protocol is found, the protocol is stripped from fname and the
-result is passed to the protocols function as the only parameter. If the
-function returns nil, the filename is removed from the list of filenames
-passed from emacsclient to the server.
-If the function returns a non nil value, that value is passed to the server
-as filename."
+Old-style links such as \"protocol://sub-protocol://param1/param2\" are
+also recognized.
+
+If a matching protocol is found, the protocol is stripped from
+fname and the result is passed to the protocol function as the
+first parameter. The second parameter will be non-nil if FNAME
+uses key=val&key2=val2-type arguments, or nil if FNAME uses
+val/val2-type arguments. If the function returns nil, the
+filename is removed from the list of filenames passed from
+emacsclient to the server. If the function returns a non-nil
+value, that value is passed to the server as filename.
+
+If the handler function is greedy, RESTOFFILES will also be passed to it.
+
+CLIENT is ignored."
(let ((sub-protocols (append org-protocol-protocol-alist
org-protocol-protocol-alist-default)))
(catch 'fname
- (let ((the-protocol (concat (regexp-quote org-protocol-the-protocol) ":/+")))
+ (let ((the-protocol (concat (regexp-quote org-protocol-the-protocol)
+ ":/+")))
(when (string-match the-protocol fname)
(dolist (prolist sub-protocols)
- (let ((proto (concat the-protocol
- (regexp-quote (plist-get (cdr prolist) :protocol)) ":/+")))
+ (let ((proto
+ (concat the-protocol
+ (regexp-quote (plist-get (cdr prolist) :protocol))
+ "\\(:/+\\|\\?\\)")))
(when (string-match proto fname)
(let* ((func (plist-get (cdr prolist) :function))
(greedy (plist-get (cdr prolist) :greedy))
(split (split-string fname proto))
- (result (if greedy restoffiles (cadr split))))
+ (result (if greedy restoffiles (cadr split)))
+ (new-style (string= (match-string 1 fname) "?")))
(when (plist-get (cdr prolist) :kill-client)
(message "Greedy org-protocol handler. Killing client.")
(server-edit))
(when (fboundp func)
(unless greedy
- (throw 'fname (funcall func result)))
- (funcall func result)
+ (throw 'fname
+ (if new-style
+ (funcall func (org-protocol-parse-parameters
+ result new-style))
+ (warn "Please update your Org Protocol handler \
+to deal with new-style links.")
+ (funcall func result))))
+ ;; Greedy protocol handlers are responsible for
+ ;; parsing their own filenames.
+ (funcall func result)
(throw 'fname t))))))))
- ;; (message "fname: %s" fname)
fname)))
(defadvice server-visit-files (before org-protocol-detect-protocol-server activate)
@@ -572,16 +650,18 @@ as filename."
;;; Org specific functions:
(defun org-protocol-create-for-org ()
- "Create a org-protocol project for the current file's Org-mode project.
+ "Create a Org protocol project for the current file's project.
The visited file needs to be part of a publishing project in
`org-publish-project-alist' for this to work. The function
delegates most of the work to `org-protocol-create'."
(interactive)
- (require 'org-publish)
+ (require 'ox-publish)
(let ((all (or (org-publish-get-project-from-filename buffer-file-name))))
(if all (org-protocol-create (cdr all))
- (message "Not in an org-project. Did mean %s?"
- (substitute-command-keys"\\[org-protocol-create]")))))
+ (message "%s"
+ (substitute-command-keys
+ "Not in an Org project. \
+Did you mean `\\[org-protocol-create]'?")))))
(defun org-protocol-create (&optional project-plist)
"Create a new org-protocol project interactively.
@@ -600,19 +680,18 @@ the cdr of an element in `org-publish-project-alist', reuse
(working-suffix (if (plist-get project-plist :base-extension)
(concat "." (plist-get project-plist :base-extension))
".org"))
- (worglet-buffer nil)
(insert-default-directory t)
(minibuffer-allow-text-properties nil))
(setq base-url (read-string "Base URL of published content: " base-url nil base-url t))
- (if (not (string-match "\\/$" base-url))
- (setq base-url (concat base-url "/")))
+ (or (string-suffix-p "/" base-url)
+ (setq base-url (concat base-url "/")))
(setq working-dir
(expand-file-name
(read-directory-name "Local working directory: " working-dir working-dir t)))
- (if (not (string-match "\\/$" working-dir))
- (setq working-dir (concat working-dir "/")))
+ (or (string-suffix-p "/" working-dir)
+ (setq working-dir (concat working-dir "/")))
(setq strip-suffix
(read-string
diff --git a/lisp/org/org-rmail.el b/lisp/org/org-rmail.el
index 80bfce920c5..31c59a13d89 100644
--- a/lisp/org/org-rmail.el
+++ b/lisp/org/org-rmail.el
@@ -1,4 +1,4 @@
-;;; org-rmail.el --- Support for links to Rmail messages from within Org-mode
+;;; org-rmail.el --- Support for Links to Rmail Messages -*- lexical-binding: t; -*-
;; Copyright (C) 2004-2017 Free Software Foundation, Inc.
@@ -24,9 +24,9 @@
;;
;;; Commentary:
-;; This file implements links to Rmail messages from within Org-mode.
-;; Org-mode loads this module by default - if this is not what you want,
-;; configure the variable `org-modules'.
+;; This file implements links to Rmail messages from within Org mode.
+;; Org mode loads this module by default - if this is not what you
+;; want, configure the variable `org-modules'.
;;; Code:
@@ -36,13 +36,14 @@
(declare-function rmail-show-message "rmail" (&optional n no-summary))
(declare-function rmail-what-message "rmail" (&optional pos))
(declare-function rmail-toggle-header "rmail" (&optional arg))
+(declare-function rmail "rmail" (&optional file-name-arg))
(declare-function rmail-widen "rmail" ())
(defvar rmail-current-message) ; From rmail.el
(defvar rmail-header-style) ; From rmail.el
+(defvar rmail-file-name) ; From rmail.el
;; Install the link type
-(org-add-link-type "rmail" 'org-rmail-open)
-(add-hook 'org-store-link-functions 'org-rmail-store-link)
+(org-link-set-parameters "rmail" :follow #'org-rmail-open :store #'org-rmail-store-link)
;; Implementation
(defun org-rmail-store-link ()
@@ -63,20 +64,11 @@
(to (mail-fetch-field "to"))
(subject (mail-fetch-field "subject"))
(date (mail-fetch-field "date"))
- (date-ts (and date (format-time-string
- (org-time-stamp-format t)
- (date-to-time date))))
- (date-ts-ia (and date (format-time-string
- (org-time-stamp-format t t)
- (date-to-time date))))
desc link)
(org-store-link-props
- :type "rmail" :from from :to to
+ :type "rmail" :from from :to to :date date
:subject subject :message-id message-id)
- (when date
- (org-add-link-props :date date :date-timestamp date-ts
- :date-timestamp-inactive date-ts-ia))
- (setq message-id (org-remove-angle-brackets message-id))
+ (setq message-id (org-unbracket-string "<" ">" message-id))
(setq desc (org-email-link-description))
(setq link (concat "rmail:" folder "#" message-id))
(org-add-link-props :link link :description desc)
diff --git a/lisp/org/org-src.el b/lisp/org/org-src.el
index 0e82cfda7b8..0e04d4b5a89 100644
--- a/lisp/org/org-src.el
+++ b/lisp/org/org-src.el
@@ -1,4 +1,4 @@
-;;; org-src.el --- Source code examples in Org
+;;; org-src.el --- Source code examples in Org -*- lexical-binding: t; -*-
;;
;; Copyright (C) 2004-2017 Free Software Foundation, Inc.
;;
@@ -26,43 +26,33 @@
;;
;;; Commentary:
-;; This file contains the code dealing with source code examples in Org-mode.
+;; This file contains the code dealing with source code examples in
+;; Org mode.
;;; Code:
+(require 'cl-lib)
(require 'org-macs)
(require 'org-compat)
(require 'ob-keys)
(require 'ob-comint)
-(eval-when-compile
- (require 'cl))
+(declare-function org-base-buffer "org" (buffer))
(declare-function org-do-remove-indentation "org" (&optional n))
-(declare-function org-at-table.el-p "org" ())
-(declare-function org-in-src-block-p "org" (&optional inside))
-(declare-function org-in-block-p "org" (names))
+(declare-function org-element-at-point "org-element" ())
+(declare-function org-element-class "org-element" (datum &optional parent))
+(declare-function org-element-context "org-element" (&optional element))
+(declare-function org-element-lineage "org-element"
+ (blob &optional types with-self))
+(declare-function org-element-property "org-element" (property element))
+(declare-function org-element-type "org-element" (element))
+(declare-function org-footnote-goto-definition "org-footnote"
+ (label &optional location))
(declare-function org-get-indentation "org" (&optional line))
(declare-function org-switch-to-buffer-other-window "org" (&rest args))
-(declare-function org-pop-to-buffer-same-window
- "org-compat" (&optional buffer-or-name norecord label))
-(declare-function org-base-buffer "org" (buffer))
+(declare-function org-trim "org" (s &optional keep-lead))
-(defcustom org-edit-src-region-extra nil
- "Additional regexps to identify regions for editing with `org-edit-src-code'.
-For examples see the function `org-edit-src-find-region-and-lang'.
-The regular expression identifying the begin marker should end with a newline,
-and the regexp marking the end line should start with a newline, to make sure
-there are kept outside the narrowed region."
- :group 'org-edit-structure
- :type '(repeat
- (list
- (regexp :tag "begin regexp")
- (regexp :tag "end regexp")
- (choice :tag "language"
- (string :tag "specify")
- (integer :tag "from match group")
- (const :tag "from `lang' element")
- (const :tag "from `style' element")))))
+(defvar org-inhibit-startup)
(defcustom org-edit-src-turn-on-auto-save nil
"Non-nil means turn `auto-save-mode' on when editing a source block.
@@ -117,28 +107,29 @@ These are the regions where each line starts with a colon."
(defcustom org-src-preserve-indentation nil
"If non-nil preserve leading whitespace characters on export.
+\\
If non-nil leading whitespace characters in source code blocks
are preserved on export, and when switching between the org
-buffer and the language mode edit buffer. If this variable is nil
-then, after editing with \\[org-edit-src-code], the
-minimum (across-lines) number of leading whitespace characters
-are removed from all lines, and the code block is uniformly
-indented according to the value of `org-edit-src-content-indentation'."
+buffer and the language mode edit buffer.
+
+When this variable is nil, after editing with `\\[org-edit-src-code]',
+the minimum (across-lines) number of leading whitespace characters
+are removed from all lines, and the code block is uniformly indented
+according to the value of `org-edit-src-content-indentation'."
:group 'org-edit-structure
:type 'boolean)
(defcustom org-edit-src-content-indentation 2
"Indentation for the content of a source code block.
+
This should be the number of spaces added to the indentation of the #+begin
line in order to compute the indentation of the block content after
-editing it with \\[org-edit-src-code]. Has no effect if
-`org-src-preserve-indentation' is non-nil."
+editing it with `\\[org-edit-src-code]'.
+
+It has no effect if `org-src-preserve-indentation' is non-nil."
:group 'org-edit-structure
:type 'integer)
-(defvar org-src-strip-leading-and-trailing-blank-lines nil
- "If non-nil, blank lines are removed when exiting the code edit buffer.")
-
(defcustom org-edit-src-persistent-message t
"Non-nil means show persistent exit help message while editing src examples.
The message is shown in the header-line, which will be created in the
@@ -146,6 +137,17 @@ first line of the window showing the editing buffer."
:group 'org-edit-structure
:type 'boolean)
+(defcustom org-src-ask-before-returning-to-edit-buffer t
+ "Non-nil means ask before switching to an existing edit buffer.
+If nil, when `org-edit-src-code' is used on a block that already
+has an active edit buffer, it will switch to that edit buffer
+immediately; otherwise it will ask whether you want to return to
+the existing edit buffer."
+ :group 'org-edit-structure
+ :version "24.4"
+ :package-version '(Org . "8.0")
+ :type 'boolean)
+
(defcustom org-src-window-setup 'reorganize-frame
"How the source code edit buffer should be displayed.
Possible values for this option are:
@@ -167,10 +169,10 @@ other-frame Use `switch-to-buffer-other-frame' to display edit buffer.
(defvar org-src-mode-hook nil
"Hook run after Org switched a source code snippet to its Emacs mode.
-This hook will run
-
-- when editing a source code snippet with `\\[org-src-mode-map]'.
-- When formatting a source code snippet for export with htmlize.
+\\
+This hook will run:
+- when editing a source code snippet with `\\[org-edit-special]'
+- when formatting a source code snippet for export with htmlize.
You may want to use this hook for example to turn off `outline-minor-mode'
or similar things which you want to have when editing a source code file,
@@ -180,7 +182,7 @@ but which mess up the display of a snippet in Org exported files.")
'(("ocaml" . tuareg) ("elisp" . emacs-lisp) ("ditaa" . artist)
("asymptote" . asy) ("dot" . fundamental) ("sqlite" . sql)
("calc" . fundamental) ("C" . c) ("cpp" . c++) ("C++" . c++)
- ("screen" . shell-script))
+ ("screen" . shell-script) ("shell" . sh) ("bash" . sh))
"Alist mapping languages to their major mode.
The key is the language name, the value is the string that should
be inserted as the name of the major mode. For many languages this is
@@ -194,451 +196,383 @@ For example, there is no ocaml-mode in Emacs, but the mode to use is
(string "Language name")
(symbol "Major mode"))))
-;;; Editing source examples
+(defcustom org-src-block-faces nil
+ "Alist of faces to be used for source-block.
+Each element is a cell of the format
-(defvar org-src-mode-map (make-sparse-keymap))
-(define-key org-src-mode-map "\C-c'" 'org-edit-src-exit)
-(define-key org-src-mode-map "\C-c\C-k" 'org-edit-src-abort)
-(define-key org-src-mode-map "\C-x\C-s" 'org-edit-src-save)
+ (\"language\" FACE)
-(defvar org-edit-src-force-single-line nil)
-(defvar org-edit-src-from-org-mode nil)
-(defvar org-edit-src-allow-write-back-p t)
-(defvar org-edit-src-picture nil)
-(defvar org-edit-src-beg-marker nil)
-(defvar org-edit-src-end-marker nil)
-(defvar org-edit-src-overlay nil)
-(defvar org-edit-src-block-indentation nil)
-(defvar org-edit-src-saved-temp-window-config nil)
+Where FACE is either a defined face or an anonymous face.
-(defcustom org-src-ask-before-returning-to-edit-buffer t
- "If nil, when org-edit-src code is used on a block that already
-has an active edit buffer, it will switch to that edit buffer
-immediately; otherwise it will ask whether you want to return to
-the existing edit buffer."
- :group 'org-edit-structure
- :version "24.4"
- :package-version '(Org . "8.0")
- :type 'boolean)
-
-(defvar org-src-babel-info nil)
+For instance, the following value would color the background of
+emacs-lisp source blocks and python source blocks in purple and
+green, respectability.
-(define-minor-mode org-src-mode
- "Minor mode for language major mode buffers generated by org.
-This minor mode is turned on in two situations:
-- when editing a source code snippet with `\\[org-src-mode-map]'.
-- When formatting a source code snippet for export with htmlize.
-There is a mode hook, and keybindings for `org-edit-src-exit' and
-`org-edit-src-save'")
-
-(defvar org-edit-src-code-timer nil)
-(defvar org-inhibit-startup)
+ \\='((\"emacs-lisp\" (:background \"#EEE2FF\"))
+ (\"python\" (:background \"#e5ffb8\")))"
+ :group 'org-edit-structure
+ :type '(repeat (list (string :tag "language")
+ (choice
+ (face :tag "Face")
+ (sexp :tag "Anonymous face"))))
+ :version "26.1"
+ :package-version '(Org . "9.0"))
-(defun org-edit-src-code (&optional context code edit-buffer-name)
- "Edit the source CODE block at point.
-The code is copied to a separate buffer and the appropriate mode
-is turned on. When done, exit with \\[org-edit-src-exit]. This will
-remove the original code in the Org buffer, and replace it with the
-edited version. An optional argument CONTEXT is used by \\[org-edit-src-save]
-when calling this function. See `org-src-window-setup' to configure
-the display of windows containing the Org buffer and the code buffer."
- (interactive)
- (if (not (or (org-in-block-p '("src" "example" "latex" "html"))
- (org-at-table.el-p)))
- (user-error "Not in a source code or example block")
- (unless (eq context 'save)
- (setq org-edit-src-saved-temp-window-config (current-window-configuration)))
- (let* ((mark (and (org-region-active-p) (mark)))
- (case-fold-search t)
- (info
- ;; If the src region consists in no lines, we insert a blank
- ;; line.
- (let* ((temp (org-edit-src-find-region-and-lang))
- (beg (nth 0 temp))
- (end (nth 1 temp)))
- (if (>= end beg) temp
- (goto-char beg)
- (insert "\n")
- (org-edit-src-find-region-and-lang))))
- (full-info (org-babel-get-src-block-info 'light))
- (org-mode-p (derived-mode-p 'org-mode)) ;; derived-mode-p is reflexive
- (beg (make-marker))
- ;; Move marker with inserted text for case when src block is
- ;; just one empty line, i.e. beg == end.
- (end (copy-marker (make-marker) t))
- (allow-write-back-p (null code))
- block-nindent total-nindent ovl lang lang-f single buffer msg
- begline markline markcol line col transmitted-variables)
- (setq beg (move-marker beg (nth 0 info))
- end (move-marker end (nth 1 info))
- msg (if allow-write-back-p
- "Edit, then exit with C-c ' (C-c and single quote) -- C-c C-k to abort"
- "Exit with C-c ' (C-c and single quote) -- C-c C-k to abort")
- code (or code (buffer-substring-no-properties beg end))
- lang (or (cdr (assoc (nth 2 info) org-src-lang-modes))
- (nth 2 info))
- lang (if (symbolp lang) (symbol-name lang) lang)
- single (nth 3 info)
- block-nindent (nth 5 info)
- lang-f (intern (concat lang "-mode"))
- begline (save-excursion (goto-char beg) (org-current-line))
- transmitted-variables
- `((org-edit-src-content-indentation
- ,org-edit-src-content-indentation)
- (org-edit-src-force-single-line ,single)
- (org-edit-src-from-org-mode ,org-mode-p)
- (org-edit-src-allow-write-back-p ,allow-write-back-p)
- (org-src-preserve-indentation ,org-src-preserve-indentation)
- (org-src-babel-info ,(org-babel-get-src-block-info 'light))
- (org-coderef-label-format
- ,(or (nth 4 info) org-coderef-label-format))
- (org-edit-src-beg-marker ,beg)
- (org-edit-src-end-marker ,end)
- (org-edit-src-block-indentation ,block-nindent)))
- (if (and mark (>= mark beg) (<= mark (1+ end)))
- (save-excursion (goto-char (min mark end))
- (setq markline (org-current-line)
- markcol (current-column))))
- (if (equal lang-f 'table.el-mode)
- (setq lang-f (lambda ()
- (text-mode)
- (if (org-bound-and-true-p flyspell-mode)
- (flyspell-mode -1))
- (table-recognize)
- (org-set-local 'org-edit-src-content-indentation 0))))
- (unless (functionp lang-f)
- (error "No such language mode: %s" lang-f))
- (save-excursion
- (if (> (point) end) (goto-char end))
- (setq line (org-current-line)
- col (current-column)))
- (if (and (setq buffer (org-edit-src-find-buffer beg end))
- (or (eq context 'save)
- (if org-src-ask-before-returning-to-edit-buffer
- (y-or-n-p "Return to existing edit buffer ([n] will revert changes)? ") t)))
- (org-src-switch-to-buffer buffer 'return)
- (when buffer
- (with-current-buffer buffer
- (if (boundp 'org-edit-src-overlay)
- (delete-overlay org-edit-src-overlay)))
- (kill-buffer buffer))
- (setq buffer (generate-new-buffer
- (or edit-buffer-name
- (org-src-construct-edit-buffer-name (buffer-name) lang))))
- (setq ovl (make-overlay beg end))
- (overlay-put ovl 'edit-buffer buffer)
- (overlay-put ovl 'help-echo "Click with mouse-1 to switch to buffer editing this segment")
- (overlay-put ovl 'face 'secondary-selection)
- (overlay-put ovl
- 'keymap
- (let ((map (make-sparse-keymap)))
- (define-key map [mouse-1] 'org-edit-src-continue)
- map))
- (overlay-put ovl :read-only "Leave me alone")
- (setq transmitted-variables
- (append transmitted-variables `((org-edit-src-overlay ,ovl))))
- (org-src-switch-to-buffer buffer 'edit)
- (if (eq single 'macro-definition)
- (setq code (replace-regexp-in-string "\\\\n" "\n" code t t)))
- (insert code)
- (remove-text-properties (point-min) (point-max)
- '(display nil invisible nil intangible nil))
- (unless (cadr (assq 'org-src-preserve-indentation transmitted-variables))
- (setq total-nindent (or (org-do-remove-indentation) 0)))
- (let ((org-inhibit-startup t))
- (condition-case e
- (funcall lang-f)
- (error
- (message "Language mode `%s' fails with: %S" lang-f (nth 1 e)))))
- (dolist (pair transmitted-variables)
- (org-set-local (car pair) (cadr pair)))
- ;; Remove protecting commas from visible part of buffer.
- (org-unescape-code-in-region (point-min) (point-max))
- (when markline
- (org-goto-line (1+ (- markline begline)))
- (org-move-to-column
- (if org-src-preserve-indentation markcol
- (max 0 (- markcol total-nindent))))
- (push-mark (point) 'no-message t)
- (setq deactivate-mark nil))
- (org-goto-line (1+ (- line begline)))
- (org-move-to-column
- (if org-src-preserve-indentation col (max 0 (- col total-nindent))))
- (org-src-mode)
- (set-buffer-modified-p nil)
- (setq buffer-file-name nil)
- (when org-edit-src-turn-on-auto-save
- (setq buffer-auto-save-file-name
- (concat (make-temp-name "org-src-")
- (format-time-string "-%Y-%d-%m") ".txt")))
- (and org-edit-src-persistent-message
- (org-set-local 'header-line-format msg))
- (let ((edit-prep-func (intern (concat "org-babel-edit-prep:" lang))))
- (when (fboundp edit-prep-func)
- (funcall edit-prep-func full-info)))
- (or org-edit-src-code-timer
- (zerop org-edit-src-auto-save-idle-delay)
- (setq org-edit-src-code-timer
- (run-with-idle-timer
- org-edit-src-auto-save-idle-delay t
- (lambda ()
- (cond
- ((org-string-match-p "\\`\\*Org Src" (buffer-name))
- (when (buffer-modified-p) (org-edit-src-save)))
- ((not (org-some (lambda (b)
- (org-string-match-p "\\`\\*Org Src"
- (buffer-name b)))
- (buffer-list)))
- (cancel-timer org-edit-src-code-timer)
- (setq org-edit-src-code-timer nil))))))))
- t)))
+(defcustom org-src-tab-acts-natively nil
+ "If non-nil, the effect of TAB in a code block is as if it were
+issued in the language major mode buffer."
+ :type 'boolean
+ :version "24.1"
+ :group 'org-babel)
-(defun org-edit-src-continue (e)
- "Continue editing source blocks." ;; Fixme: be more accurate
- (interactive "e")
- (mouse-set-point e)
- (let ((buf (get-char-property (point) 'edit-buffer)))
- (if buf (org-src-switch-to-buffer buf 'continue)
- (error "Something is wrong here"))))
-(defun org-src-switch-to-buffer (buffer context)
- (case org-src-window-setup
- ('current-window
- (org-pop-to-buffer-same-window buffer))
- ('other-window
- (switch-to-buffer-other-window buffer))
- ('other-frame
- (case context
- ('exit
- (let ((frame (selected-frame)))
- (switch-to-buffer-other-frame buffer)
- (delete-frame frame)))
- ('save
- (kill-buffer (current-buffer))
- (org-pop-to-buffer-same-window buffer))
- (t
- (switch-to-buffer-other-frame buffer))))
- ('reorganize-frame
- (if (eq context 'edit) (delete-other-windows))
- (org-switch-to-buffer-other-window buffer)
- (if (eq context 'exit) (delete-other-windows)))
- ('switch-invisibly
- (set-buffer buffer))
- (t
- (message "Invalid value %s for org-src-window-setup"
- (symbol-name org-src-window-setup))
- (org-pop-to-buffer-same-window buffer))))
-
-(defun org-src-construct-edit-buffer-name (org-buffer-name lang)
+
+;;; Internal functions and variables
+
+(defvar org-src--allow-write-back t)
+(defvar org-src--auto-save-timer nil)
+(defvar org-src--babel-info nil)
+(defvar org-src--beg-marker nil)
+(defvar org-src--block-indentation nil)
+(defvar org-src--end-marker nil)
+(defvar org-src--from-org-mode nil)
+(defvar org-src--overlay nil)
+(defvar org-src--preserve-indentation nil)
+(defvar org-src--remote nil)
+(defvar org-src--saved-temp-window-config nil)
+(defvar org-src--source-type nil
+ "Type of element being edited, as a symbol.")
+(defvar org-src--tab-width nil
+ "Contains `tab-width' value from Org source buffer.
+However, if `indent-tabs-mode' is nil in that buffer, its value
+is 0.")
+
+(defun org-src--construct-edit-buffer-name (org-buffer-name lang)
"Construct the buffer name for a source editing buffer."
(concat "*Org Src " org-buffer-name "[ " lang " ]*"))
-(defun org-src-edit-buffer-p (&optional buffer)
- "Test whether BUFFER (or the current buffer if BUFFER is nil)
-is a source block editing buffer."
- (let ((buffer (org-base-buffer (or buffer (current-buffer)))))
- (and (buffer-name buffer)
- (string-match "\\`*Org Src " (buffer-name buffer))
- (local-variable-p 'org-edit-src-beg-marker buffer)
- (local-variable-p 'org-edit-src-end-marker buffer))))
-
-(defun org-edit-src-find-buffer (beg end)
- "Find a source editing buffer that is already editing the region BEG to END."
+(defun org-src--edit-buffer (beg end)
+ "Return buffer editing area between BEG and END.
+Return nil if there is no such buffer."
(catch 'exit
- (mapc
- (lambda (b)
- (with-current-buffer b
- (if (and (string-match "\\`*Org Src " (buffer-name))
- (local-variable-p 'org-edit-src-beg-marker (current-buffer))
- (local-variable-p 'org-edit-src-end-marker (current-buffer))
- (equal beg org-edit-src-beg-marker)
- (equal end org-edit-src-end-marker))
- (throw 'exit (current-buffer)))))
- (buffer-list))
- nil))
+ (dolist (b (buffer-list))
+ (with-current-buffer b
+ (and (org-src-edit-buffer-p)
+ (= beg org-src--beg-marker)
+ (eq (marker-buffer beg) (marker-buffer org-src--beg-marker))
+ (= end org-src--end-marker)
+ (eq (marker-buffer end) (marker-buffer org-src--end-marker))
+ (throw 'exit b))))))
+
+(defun org-src--source-buffer ()
+ "Return source buffer edited by current buffer."
+ (unless (org-src-edit-buffer-p) (error "Not in a source buffer"))
+ (or (marker-buffer org-src--beg-marker)
+ (error "No source buffer available for current editing session")))
+
+(defun org-src--get-lang-mode (lang)
+ "Return major mode that should be used for LANG.
+LANG is a string, and the returned major mode is a symbol."
+ (intern
+ (concat
+ (let ((l (or (cdr (assoc lang org-src-lang-modes)) lang)))
+ (if (symbolp l) (symbol-name l) l))
+ "-mode")))
-(defun org-edit-fixed-width-region ()
- "Edit the fixed-width ascii drawing at point.
-This must be a region where each line starts with a colon followed by
-a space character.
-An new buffer is created and the fixed-width region is copied into it,
-and the buffer is switched into `artist-mode' for editing. When done,
-exit with \\[org-edit-src-exit]. The edited text will then replace
-the fragment in the Org-mode buffer."
- (interactive)
- (let ((line (org-current-line))
- (col (current-column))
- (case-fold-search t)
- (msg "Edit, then exit with C-c ' (C-c and single quote) -- C-c C-k to abort")
- (org-mode-p (derived-mode-p 'org-mode))
- (beg (make-marker))
- (end (make-marker))
- block-nindent ovl beg1 end1 code begline buffer)
- (beginning-of-line 1)
- (if (looking-at "[ \t]*[^:\n \t]")
- nil
- (if (looking-at "[ \t]*\\(\n\\|\\'\\)")
- (setq beg1 (point) end1 beg1)
- (save-excursion
- (if (re-search-backward "^[ \t]*[^: \t]" nil 'move)
- (setq beg1 (point-at-bol 2))
- (setq beg1 (point))))
- (save-excursion
- (if (re-search-forward "^[ \t]*[^: \t]" nil 'move)
- (setq end1 (1- (match-beginning 0)))
- (setq end1 (point))))
- (org-goto-line line))
- (setq beg (move-marker beg beg1)
- end (move-marker end end1)
- code (buffer-substring-no-properties beg end)
- begline (save-excursion (goto-char beg) (org-current-line)))
- (if (and (setq buffer (org-edit-src-find-buffer beg end))
- (y-or-n-p "Return to existing edit buffer ([n] will revert changes)? "))
- (org-pop-to-buffer-same-window buffer)
- (when buffer
- (with-current-buffer buffer
- (if (boundp 'org-edit-src-overlay)
- (delete-overlay org-edit-src-overlay)))
- (kill-buffer buffer))
- (setq buffer (generate-new-buffer
- (org-src-construct-edit-buffer-name
- (buffer-name) "Fixed Width")))
- (setq ovl (make-overlay beg end))
- (overlay-put ovl 'face 'secondary-selection)
- (overlay-put ovl 'edit-buffer buffer)
- (overlay-put ovl 'help-echo "Click with mouse-1 to switch to buffer editing this segment")
- (overlay-put ovl 'face 'secondary-selection)
- (overlay-put ovl
- 'keymap
- (let ((map (make-sparse-keymap)))
- (define-key map [mouse-1] 'org-edit-src-continue)
- map))
- (overlay-put ovl :read-only "Leave me alone")
- (org-pop-to-buffer-same-window buffer)
- (insert code)
+(defun org-src--coordinates (pos beg end)
+ "Return coordinates of POS relatively to BEG and END.
+POS, BEG and END are buffer positions. Return value is either
+a cons cell (LINE . COLUMN) or symbol `end'. See also
+`org-src--goto-coordinates'."
+ (if (>= pos end) 'end
+ (org-with-wide-buffer
+ (goto-char (max beg pos))
+ (cons (count-lines beg (line-beginning-position))
+ ;; Column is relative to the end of line to avoid problems of
+ ;; comma escaping or colons appended in front of the line.
+ (- (current-column)
+ (progn (end-of-line) (current-column)))))))
+
+(defun org-src--goto-coordinates (coord beg end)
+ "Move to coordinates COORD relatively to BEG and END.
+COORD are coordinates, as returned by `org-src--coordinates',
+which see. BEG and END are buffer positions."
+ (goto-char
+ (if (eq coord 'end) (max (1- end) beg)
+ ;; If BEG happens to be located outside of the narrowed part of
+ ;; the buffer, widen it first.
+ (org-with-wide-buffer
+ (goto-char beg)
+ (forward-line (car coord))
+ (end-of-line)
+ (org-move-to-column (max (+ (current-column) (cdr coord)) 0))
+ (point)))))
+
+(defun org-src--contents-area (datum)
+ "Return contents boundaries of DATUM.
+DATUM is an element or object. Return a list (BEG END CONTENTS)
+where BEG and END are buffer positions and CONTENTS is a string."
+ (let ((type (org-element-type datum)))
+ (org-with-wide-buffer
+ (cond
+ ((eq type 'footnote-definition)
+ (let* ((beg (progn
+ (goto-char (org-element-property :post-affiliated datum))
+ (search-forward "]")))
+ (end (or (org-element-property :contents-end datum) beg)))
+ (list beg end (buffer-substring-no-properties beg end))))
+ ((eq type 'inline-src-block)
+ (let ((beg (progn (goto-char (org-element-property :begin datum))
+ (search-forward "{" (line-end-position) t)))
+ (end (progn (goto-char (org-element-property :end datum))
+ (search-backward "}" (line-beginning-position) t))))
+ (list beg end (buffer-substring-no-properties beg end))))
+ ((org-element-property :contents-begin datum)
+ (let ((beg (org-element-property :contents-begin datum))
+ (end (org-element-property :contents-end datum)))
+ (list beg end (buffer-substring-no-properties beg end))))
+ ((memq type '(example-block export-block src-block))
+ (list (progn (goto-char (org-element-property :post-affiliated datum))
+ (line-beginning-position 2))
+ (progn (goto-char (org-element-property :end datum))
+ (skip-chars-backward " \r\t\n")
+ (line-beginning-position 1))
+ (org-element-property :value datum)))
+ ((memq type '(fixed-width table))
+ (let ((beg (org-element-property :post-affiliated datum))
+ (end (progn (goto-char (org-element-property :end datum))
+ (skip-chars-backward " \r\t\n")
+ (line-beginning-position 2))))
+ (list beg
+ end
+ (if (eq type 'fixed-width) (org-element-property :value datum)
+ (buffer-substring-no-properties beg end)))))
+ (t (error "Unsupported element or object: %s" type))))))
+
+(defun org-src--make-source-overlay (beg end edit-buffer)
+ "Create overlay between BEG and END positions and return it.
+EDIT-BUFFER is the buffer currently editing area between BEG and
+END."
+ (let ((overlay (make-overlay beg end)))
+ (overlay-put overlay 'face 'secondary-selection)
+ (overlay-put overlay 'edit-buffer edit-buffer)
+ (overlay-put overlay 'help-echo
+ "Click with mouse-1 to switch to buffer editing this segment")
+ (overlay-put overlay 'face 'secondary-selection)
+ (overlay-put overlay 'keymap
+ (let ((map (make-sparse-keymap)))
+ (define-key map [mouse-1] 'org-edit-src-continue)
+ map))
+ (let ((read-only
+ (list
+ (lambda (&rest _)
+ (user-error
+ "Cannot modify an area being edited in a dedicated buffer")))))
+ (overlay-put overlay 'modification-hooks read-only)
+ (overlay-put overlay 'insert-in-front-hooks read-only)
+ (overlay-put overlay 'insert-behind-hooks read-only))
+ overlay))
+
+(defun org-src--remove-overlay ()
+ "Remove overlay from current source buffer."
+ (when (overlayp org-src--overlay) (delete-overlay org-src--overlay)))
+
+(defun org-src--on-datum-p (datum)
+ "Non-nil when point is on DATUM.
+DATUM is an element or an object. Consider blank lines or white
+spaces after it as being outside."
+ (and (>= (point) (org-element-property :begin datum))
+ (<= (point)
+ (org-with-wide-buffer
+ (goto-char (org-element-property :end datum))
+ (skip-chars-backward " \r\t\n")
+ (if (eq (org-element-class datum) 'element)
+ (line-end-position)
+ (point))))))
+
+(defun org-src--contents-for-write-back ()
+ "Return buffer contents in a format appropriate for write back.
+Assume point is in the corresponding edit buffer."
+ (let ((indentation-offset
+ (if org-src--preserve-indentation 0
+ (+ (or org-src--block-indentation 0)
+ (if (memq org-src--source-type '(example-block src-block))
+ org-edit-src-content-indentation
+ 0))))
+ (use-tabs? (and (> org-src--tab-width 0) t))
+ (source-tab-width org-src--tab-width)
+ (contents (org-with-wide-buffer (buffer-string)))
+ (write-back org-src--allow-write-back))
+ (with-temp-buffer
+ ;; Reproduce indentation parameters from source buffer.
+ (setq-local indent-tabs-mode use-tabs?)
+ (when (> source-tab-width 0) (setq-local tab-width source-tab-width))
+ ;; Apply WRITE-BACK function on edit buffer contents.
+ (insert (org-no-properties contents))
+ (goto-char (point-min))
+ (when (functionp write-back) (save-excursion (funcall write-back)))
+ ;; Add INDENTATION-OFFSET to every non-empty line in buffer,
+ ;; unless indentation is meant to be preserved.
+ (when (> indentation-offset 0)
+ (while (not (eobp))
+ (skip-chars-forward " \t")
+ (unless (eolp) ;ignore blank lines
+ (let ((i (current-column)))
+ (delete-region (line-beginning-position) (point))
+ (indent-to (+ i indentation-offset))))
+ (forward-line)))
+ (buffer-string))))
+
+(defun org-src--edit-element
+ (datum name &optional major write-back contents remote)
+ "Edit DATUM contents in a dedicated buffer NAME.
+
+MAJOR is the major mode used in the edit buffer. A nil value is
+equivalent to `fundamental-mode'.
+
+When WRITE-BACK is non-nil, assume contents will replace original
+region. Moreover, if it is a function, apply it in the edit
+buffer, from point min, before returning the contents.
+
+When CONTENTS is non-nil, display them in the edit buffer.
+Otherwise, show DATUM contents as specified by
+`org-src--contents-area'.
+
+When REMOTE is non-nil, do not try to preserve point or mark when
+moving from the edit area to the source.
+
+Leave point in edit buffer."
+ (setq org-src--saved-temp-window-config (current-window-configuration))
+ (let* ((area (org-src--contents-area datum))
+ (beg (copy-marker (nth 0 area)))
+ (end (copy-marker (nth 1 area) t))
+ (old-edit-buffer (org-src--edit-buffer beg end))
+ (contents (or contents (nth 2 area))))
+ (if (and old-edit-buffer
+ (or (not org-src-ask-before-returning-to-edit-buffer)
+ (y-or-n-p "Return to existing edit buffer ([n] will revert changes)? ")))
+ ;; Move to existing buffer.
+ (org-src-switch-to-buffer old-edit-buffer 'return)
+ ;; Discard old edit buffer.
+ (when old-edit-buffer
+ (with-current-buffer old-edit-buffer (org-src--remove-overlay))
+ (kill-buffer old-edit-buffer))
+ (let* ((org-mode-p (derived-mode-p 'org-mode))
+ (source-tab-width (if indent-tabs-mode tab-width 0))
+ (type (org-element-type datum))
+ (ind (org-with-wide-buffer
+ (goto-char (org-element-property :begin datum))
+ (org-get-indentation)))
+ (preserve-ind
+ (and (memq type '(example-block src-block))
+ (or (org-element-property :preserve-indent datum)
+ org-src-preserve-indentation)))
+ ;; Store relative positions of mark (if any) and point
+ ;; within the edited area.
+ (point-coordinates (and (not remote)
+ (org-src--coordinates (point) beg end)))
+ (mark-coordinates (and (not remote)
+ (org-region-active-p)
+ (let ((m (mark)))
+ (and (>= m beg) (>= end m)
+ (org-src--coordinates m beg end)))))
+ ;; Generate a new edit buffer.
+ (buffer (generate-new-buffer name))
+ ;; Add an overlay on top of source.
+ (overlay (org-src--make-source-overlay beg end buffer)))
+ ;; Switch to edit buffer.
+ (org-src-switch-to-buffer buffer 'edit)
+ ;; Insert contents.
+ (insert contents)
(remove-text-properties (point-min) (point-max)
'(display nil invisible nil intangible nil))
- (setq block-nindent (or (org-do-remove-indentation) 0))
- (cond
- ((eq org-edit-fixed-width-region-mode 'artist-mode)
- (fundamental-mode)
- (artist-mode 1))
- (t (funcall org-edit-fixed-width-region-mode)))
- (set (make-local-variable 'org-edit-src-force-single-line) nil)
- (set (make-local-variable 'org-edit-src-from-org-mode) org-mode-p)
- (set (make-local-variable 'org-edit-src-picture) t)
- (goto-char (point-min))
- (while (re-search-forward "^[ \t]*: ?" nil t)
- (replace-match ""))
- (org-goto-line (1+ (- line begline)))
- (org-move-to-column (max 0 (- col block-nindent 2)))
- (org-set-local 'org-edit-src-beg-marker beg)
- (org-set-local 'org-edit-src-end-marker end)
- (org-set-local 'org-edit-src-overlay ovl)
- (org-set-local 'org-edit-src-block-indentation block-nindent)
- (org-set-local 'org-edit-src-content-indentation 0)
- (org-set-local 'org-src-preserve-indentation nil)
- (org-src-mode)
+ (unless preserve-ind (org-do-remove-indentation))
(set-buffer-modified-p nil)
- (and org-edit-src-persistent-message
- (org-set-local 'header-line-format msg)))
- (message "%s" msg)
- t)))
+ (setq buffer-file-name nil)
+ ;; Start major mode.
+ (if (not major) (fundamental-mode)
+ (let ((org-inhibit-startup t))
+ (condition-case e (funcall major)
+ (error (message "Language mode `%s' fails with: %S"
+ major (nth 1 e))))))
+ ;; Transmit buffer-local variables for exit function. It must
+ ;; be done after initializing major mode, as this operation
+ ;; may reset them otherwise.
+ (setq-local org-src--tab-width source-tab-width)
+ (setq-local org-src--from-org-mode org-mode-p)
+ (setq-local org-src--beg-marker beg)
+ (setq-local org-src--end-marker end)
+ (setq-local org-src--remote remote)
+ (setq-local org-src--source-type type)
+ (setq-local org-src--block-indentation ind)
+ (setq-local org-src--preserve-indentation preserve-ind)
+ (setq-local org-src--overlay overlay)
+ (setq-local org-src--allow-write-back write-back)
+ ;; Start minor mode.
+ (org-src-mode)
+ ;; Move mark and point in edit buffer to the corresponding
+ ;; location.
+ (if remote
+ (progn
+ ;; Put point at first non read-only character after
+ ;; leading blank.
+ (goto-char
+ (or (text-property-any (point-min) (point-max) 'read-only nil)
+ (point-max)))
+ (skip-chars-forward " \r\t\n"))
+ ;; Set mark and point.
+ (when mark-coordinates
+ (org-src--goto-coordinates mark-coordinates (point-min) (point-max))
+ (push-mark (point) 'no-message t)
+ (setq deactivate-mark nil))
+ (org-src--goto-coordinates
+ point-coordinates (point-min) (point-max)))))))
+
+
+
+;;; Fontification of source blocks
-(defun org-edit-src-find-region-and-lang ()
- "Find the region and language for a local edit.
-Return a list with beginning and end of the region, a string representing
-the language, a switch telling if the content should be in a single line."
- (let ((re-list
- (append
- org-edit-src-region-extra
- '(
- ("[^<]*>[ \t]*\n?" "\n?[ \t]*" lang)
- ("[^<]*>[ \t]*\n?" "\n?[ \t]*" style)
- ("[ \t]*\n?" "\n?[ \t]*" "fundamental")
- ("[ \t]*\n?" "\n?[ \t]*" "emacs-lisp")
- ("[ \t]*\n?" "\n?[ \t]*" "perl")
- ("[ \t]*\n?" "\n?[ \t]*" "python")
- ("[ \t]*\n?" "\n?[ \t]*" "ruby")
- ("^[ \t]*#\\+begin_src\\( \\([^ \t\n]+\\)\\)?.*\n" "\n[ \t]*#\\+end_src" 2)
- ("^[ \t]*#\\+begin_example.*\n" "\n[ \t]*#\\+end_example" "fundamental")
- ("^[ \t]*#\\+html:" "\n" "html" single-line)
- ("^[ \t]*#\\+begin_html.*\n" "\n[ \t]*#\\+end_html" "html")
- ("^[ \t]*#\\+latex:" "\n" "latex" single-line)
- ("^[ \t]*#\\+begin_latex.*\n" "\n[ \t]*#\\+end_latex" "latex")
- ("^[ \t]*#\\+ascii:" "\n" "fundamental" single-line)
- ("^[ \t]*#\\+begin_ascii.*\n" "\n[ \t]*#\\+end_ascii" "fundamental")
- ("^[ \t]*#\\+macro:[ \t]+\\S-+\\( \\|$\\)"
- "\n" "fundamental" macro-definition)
- )))
- (pos (point))
- re1 re2 single beg end lang lfmt match-re1 ind entry)
- (catch 'exit
- (while (setq entry (pop re-list))
- (setq re1 (car entry) re2 (nth 1 entry) lang (nth 2 entry)
- single (nth 3 entry))
- (save-excursion
- (if (or (looking-at re1)
- (re-search-backward re1 nil t))
- (progn
- (setq match-re1 (match-string 0))
- (setq beg (match-end 0)
- lang (org-edit-src-get-lang lang)
- lfmt (org-edit-src-get-label-format match-re1)
- ind (org-edit-src-get-indentation (match-beginning 0)))
- (if (and (re-search-forward re2 nil t)
- (>= (match-end 0) pos))
- (throw 'exit (list beg (match-beginning 0)
- lang single lfmt ind))))
- (if (or (looking-at re2)
- (re-search-forward re2 nil t))
- (progn
- (setq end (match-beginning 0))
- (if (and (re-search-backward re1 nil t)
- (<= (match-beginning 0) pos))
- (progn
- (setq lfmt (org-edit-src-get-label-format
- (match-string 0))
- ind (org-edit-src-get-indentation
- (match-beginning 0)))
- (throw 'exit
- (list (match-end 0) end
- (org-edit-src-get-lang lang)
- single lfmt ind)))))))))
- (when (org-at-table.el-p)
- (re-search-backward "^[\t]*[^ \t|\\+]" nil t)
- (setq beg (1+ (point-at-eol)))
- (goto-char beg)
- (or (re-search-forward "^[\t]*[^ \t|\\+]" nil t)
- (progn (goto-char (point-max)) (newline)))
- (setq end (1- (point-at-bol)))
- (throw 'exit (list beg end 'table.el nil nil 0))))))
-
-(defun org-edit-src-get-lang (lang)
- "Extract the src language."
- (let ((m (match-string 0)))
- (cond
- ((stringp lang) lang)
- ((integerp lang) (match-string lang))
- ((and (eq lang 'lang)
- (string-match "\\ cnt 0))
- (goto-char (point-max)) (insert "\\n")))
- (goto-char (point-min))
- (if (looking-at "\\s-*") (replace-match " ")))
- (when (and (org-bound-and-true-p org-edit-src-from-org-mode)
- (not fixed-width-p))
- (org-escape-code-in-region (point-min) (point-max))
- (setq delta (+ delta
- (save-excursion
- (org-goto-line line)
- (if (looking-at "[ \t]*\\(,,\\)?\\(\\*\\|#+\\)") 1
- 0)))))
- (when (org-bound-and-true-p org-edit-src-picture)
- (setq preserve-indentation nil)
- (untabify (point-min) (point-max))
- (goto-char (point-min))
- (while (re-search-forward "^" nil t)
- (replace-match ": ")))
- (unless (or single preserve-indentation (= total-nindent 0))
- (setq indent (make-string total-nindent ?\ ))
- (goto-char (point-min))
- (while (re-search-forward "\\(^\\).+" nil t)
- (replace-match indent nil nil nil 1)))
- (if (org-bound-and-true-p org-edit-src-picture)
- (setq total-nindent (+ total-nindent 2)))
- (setq code (buffer-string))
- (when (eq context 'save)
- (erase-buffer)
- (insert bufstr))
- (set-buffer-modified-p nil))
- (org-src-switch-to-buffer (marker-buffer beg) (or context 'exit))
- (if (eq context 'save) (save-buffer)
- (with-current-buffer buffer
- (set-buffer-modified-p nil))
- (kill-buffer buffer))
- (goto-char beg)
- (when allow-write-back-p
- (undo-boundary)
- (delete-region beg (max beg end))
- (unless (string-match "\\`[ \t]*\\'" code)
- (insert code))
- ;; Make sure the overlay stays in place
- (when (eq context 'save) (move-overlay ovl beg (point)))
- (goto-char beg)
- (if single (just-one-space)))
- (if (memq t (mapcar (lambda (overlay)
- (eq (overlay-get overlay 'invisible)
- 'org-hide-block))
- (overlays-at (point))))
- ;; Block is hidden; put point at start of block
- (beginning-of-line 0)
- ;; Block is visible, put point where it was in the code buffer
- (when allow-write-back-p
- (org-goto-line (1- (+ (org-current-line) line)))
- (org-move-to-column (if preserve-indentation col (+ col total-nindent delta)))))
- (unless (eq context 'save)
- (move-marker beg nil)
- (move-marker end nil)))
- (unless (eq context 'save)
- (when org-edit-src-saved-temp-window-config
- (set-window-configuration org-edit-src-saved-temp-window-config)
- (setq org-edit-src-saved-temp-window-config nil))))
-
-(defun org-edit-src-abort ()
- "Abort editing of the src code and return to the Org buffer."
- (interactive)
- (let (org-edit-src-allow-write-back-p)
- (org-edit-src-exit 'exit)))
-
-(defmacro org-src-in-org-buffer (&rest body)
- `(let ((p (point)) (m (mark)) (ul buffer-undo-list) msg)
- (save-window-excursion
- (org-edit-src-exit 'save)
- ,@body
- (setq msg (current-message))
- (if (eq org-src-window-setup 'other-frame)
- (let ((org-src-window-setup 'current-window))
- (org-edit-src-code 'save))
- (org-edit-src-code 'save)))
- (setq buffer-undo-list ul)
- (push-mark m 'nomessage)
- (goto-char (min p (point-max)))
- (message (or msg ""))))
-(def-edebug-spec org-src-in-org-buffer (body))
-(defun org-edit-src-save ()
- "Save parent buffer with current state source-code buffer."
- (interactive)
- (if (string-match "Fixed Width" (buffer-name))
- (user-error "%s" "Use C-c ' to save and exit, C-c C-k to abort editing")
- (org-src-in-org-buffer (save-buffer))))
+
+;;; Org src minor mode
-(declare-function org-babel-tangle "ob-tangle" (&optional arg target-file lang))
+(defvar org-src-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map "\C-c'" 'org-edit-src-exit)
+ (define-key map "\C-c\C-k" 'org-edit-src-abort)
+ (define-key map "\C-x\C-s" 'org-edit-src-save)
+ map))
-(defun org-src-tangle (arg)
- "Tangle the parent buffer."
- (interactive)
- (org-src-in-org-buffer (org-babel-tangle arg)))
+(define-minor-mode org-src-mode
+ "Minor mode for language major mode buffers generated by Org.
+\\
+This minor mode is turned on in two situations:
+ - when editing a source code snippet with `\\[org-edit-special]'
+ - when formatting a source code snippet for export with htmlize.
+
+\\{org-src-mode-map}
+
+See also `org-src-mode-hook'."
+ nil " OrgSrc" nil
+ (when org-edit-src-persistent-message
+ (setq-local
+ header-line-format
+ (substitute-command-keys
+ (if org-src--allow-write-back
+ "Edit, then exit with `\\[org-edit-src-exit]' or abort with \
+`\\[org-edit-src-abort]'"
+ "Exit with `\\[org-edit-src-exit]' or abort with \
+`\\[org-edit-src-abort]'"))))
+ ;; Possibly activate various auto-save features (for the edit buffer
+ ;; or the source buffer).
+ (when org-edit-src-turn-on-auto-save
+ (setq buffer-auto-save-file-name
+ (concat (make-temp-name "org-src-")
+ (format-time-string "-%Y-%d-%m")
+ ".txt")))
+ (unless (or org-src--auto-save-timer (zerop org-edit-src-auto-save-idle-delay))
+ (setq org-src--auto-save-timer
+ (run-with-idle-timer
+ org-edit-src-auto-save-idle-delay t
+ (lambda ()
+ (save-excursion
+ (let (edit-flag)
+ (dolist (b (buffer-list))
+ (with-current-buffer b
+ (when (org-src-edit-buffer-p)
+ (unless edit-flag (setq edit-flag t))
+ (when (buffer-modified-p) (org-edit-src-save)))))
+ (unless edit-flag
+ (cancel-timer org-src--auto-save-timer)
+ (setq org-src--auto-save-timer nil)))))))))
(defun org-src-mode-configure-edit-buffer ()
- (when (org-bound-and-true-p org-edit-src-from-org-mode)
- (org-add-hook 'kill-buffer-hook
- #'(lambda () (delete-overlay org-edit-src-overlay)) nil 'local)
- (if (org-bound-and-true-p org-edit-src-allow-write-back-p)
+ (when (bound-and-true-p org-src--from-org-mode)
+ (add-hook 'kill-buffer-hook #'org-src--remove-overlay nil 'local)
+ (if (bound-and-true-p org-src--allow-write-back)
(progn
(setq buffer-offer-save t)
(setq buffer-file-name
- (concat (buffer-file-name (marker-buffer org-edit-src-beg-marker))
+ (concat (buffer-file-name (marker-buffer org-src--beg-marker))
"[" (buffer-name) "]"))
- (if (featurep 'xemacs)
- (progn
- (make-variable-buffer-local 'write-contents-hooks) ; needed only for 21.4
- (setq write-contents-hooks '(org-edit-src-save)))
- (setq write-contents-functions '(org-edit-src-save))))
+ (setq-local write-contents-functions '(org-edit-src-save)))
(setq buffer-read-only t))))
-(org-add-hook 'org-src-mode-hook 'org-src-mode-configure-edit-buffer)
+(add-hook 'org-src-mode-hook #'org-src-mode-configure-edit-buffer)
+
+
+;;; Babel related functions
(defun org-src-associate-babel-session (info)
"Associate edit buffer with comint session."
(interactive)
- (let ((session (cdr (assoc :session (nth 2 info)))))
+ (let ((session (cdr (assq :session (nth 2 info)))))
(and session (not (string= session "none"))
(org-babel-comint-buffer-livep session)
(let ((f (intern (format "org-babel-%s-associate-session"
@@ -843,18 +690,22 @@ with \",*\", \",#+\", \",,*\" and \",,#+\"."
(and (fboundp f) (funcall f session))))))
(defun org-src-babel-configure-edit-buffer ()
- (when org-src-babel-info
- (org-src-associate-babel-session org-src-babel-info)))
+ (when org-src--babel-info
+ (org-src-associate-babel-session org-src--babel-info)))
+
+(add-hook 'org-src-mode-hook #'org-src-babel-configure-edit-buffer)
+
+
+;;; Public API
-(org-add-hook 'org-src-mode-hook 'org-src-babel-configure-edit-buffer)
(defmacro org-src-do-at-code-block (&rest body)
- "Execute a command from an edit buffer in the Org-mode buffer."
- `(let ((beg-marker org-edit-src-beg-marker))
- (if beg-marker
- (with-current-buffer (marker-buffer beg-marker)
- (goto-char (marker-position beg-marker))
- ,@body))))
-(def-edebug-spec org-src-do-at-code-block (body))
+ "Execute BODY from an edit buffer in the Org mode buffer."
+ (declare (debug (body)))
+ `(let ((beg-marker org-src--beg-marker))
+ (when beg-marker
+ (with-current-buffer (marker-buffer beg-marker)
+ (goto-char beg-marker)
+ ,@body))))
(defun org-src-do-key-sequence-at-code-block (&optional key)
"Execute key sequence at code block in the source Org buffer.
@@ -878,81 +729,375 @@ Org-babel commands."
(if (equal key (kbd "C-g")) (keyboard-quit)
(org-edit-src-save)
(org-src-do-at-code-block
- (call-interactively
- (lookup-key org-babel-map key)))))
+ (call-interactively (lookup-key org-babel-map key)))))
-(defcustom org-src-tab-acts-natively nil
- "If non-nil, the effect of TAB in a code block is as if it were
-issued in the language major mode buffer."
- :type 'boolean
- :version "24.1"
- :group 'org-babel)
+(defun org-src-edit-buffer-p (&optional buffer)
+ "Non-nil when current buffer is a source editing buffer.
+If BUFFER is non-nil, test it instead."
+ (let ((buffer (org-base-buffer (or buffer (current-buffer)))))
+ (and (buffer-live-p buffer)
+ (local-variable-p 'org-src--beg-marker buffer)
+ (local-variable-p 'org-src--end-marker buffer))))
+
+(defun org-src-switch-to-buffer (buffer context)
+ (pcase org-src-window-setup
+ (`current-window (pop-to-buffer-same-window buffer))
+ (`other-window
+ (switch-to-buffer-other-window buffer))
+ (`other-frame
+ (pcase context
+ (`exit
+ (let ((frame (selected-frame)))
+ (switch-to-buffer-other-frame buffer)
+ (delete-frame frame)))
+ (`save
+ (kill-buffer (current-buffer))
+ (pop-to-buffer-same-window buffer))
+ (_ (switch-to-buffer-other-frame buffer))))
+ (`reorganize-frame
+ (when (eq context 'edit) (delete-other-windows))
+ (org-switch-to-buffer-other-window buffer)
+ (when (eq context 'exit) (delete-other-windows)))
+ (`switch-invisibly (set-buffer buffer))
+ (_
+ (message "Invalid value %s for `org-src-window-setup'"
+ org-src-window-setup)
+ (pop-to-buffer-same-window buffer))))
+
+(defun org-src-coderef-format (&optional element)
+ "Return format string for block at point.
+
+When optional argument ELEMENT is provided, use that block.
+Otherwise, assume point is either at a source block, at an
+example block.
+
+If point is in an edit buffer, retrieve format string associated
+to the remote source block."
+ (cond
+ ((and element (org-element-property :label-fmt element)))
+ ((org-src-edit-buffer-p) (org-src-do-at-code-block (org-src-coderef-format)))
+ ((org-element-property :label-fmt (org-element-at-point)))
+ (t org-coderef-label-format)))
+
+(defun org-src-coderef-regexp (fmt &optional label)
+ "Return regexp matching a coderef format string FMT.
+
+When optional argument LABEL is non-nil, match coderef for that
+label only.
+
+Match group 1 contains the full coderef string with surrounding
+white spaces. Match group 2 contains the same string without any
+surrounding space. Match group 3 contains the label.
+
+A coderef format regexp can only match at the end of a line."
+ (format "\\([ \t]*\\(%s\\)[ \t]*\\)$"
+ (replace-regexp-in-string
+ "%s"
+ (if label (regexp-quote label) "\\([-a-zA-Z0-9_][-a-zA-Z0-9_ ]*\\)")
+ (regexp-quote fmt)
+ nil t)))
+
+(defun org-edit-footnote-reference ()
+ "Edit definition of footnote reference at point."
+ (interactive)
+ (let* ((context (org-element-context))
+ (label (org-element-property :label context)))
+ (unless (and (eq (org-element-type context) 'footnote-reference)
+ (org-src--on-datum-p context))
+ (user-error "Not on a footnote reference"))
+ (unless label (user-error "Cannot edit remotely anonymous footnotes"))
+ (let* ((definition (org-with-wide-buffer
+ (org-footnote-goto-definition label)
+ (backward-char)
+ (org-element-context)))
+ (inline? (eq 'footnote-reference (org-element-type definition)))
+ (contents
+ (org-with-wide-buffer
+ (buffer-substring-no-properties
+ (or (org-element-property :post-affiliated definition)
+ (org-element-property :begin definition))
+ (cond
+ (inline? (1+ (org-element-property :contents-end definition)))
+ ((org-element-property :contents-end definition))
+ (t (goto-char (org-element-property :post-affiliated definition))
+ (line-end-position)))))))
+ (add-text-properties
+ 0
+ (progn (string-match (if inline? "\\`\\[fn:.*?:" "\\`.*?\\]") contents)
+ (match-end 0))
+ '(read-only "Cannot edit footnote label" front-sticky t rear-nonsticky t)
+ contents)
+ (when inline?
+ (let ((l (length contents)))
+ (add-text-properties
+ (1- l) l
+ '(read-only "Cannot edit past footnote reference"
+ front-sticky nil rear-nonsticky nil)
+ contents)))
+ (org-src--edit-element
+ definition
+ (format "*Edit footnote [%s]*" label)
+ #'org-mode
+ (lambda ()
+ (if (not inline?) (delete-region (point) (search-forward "]"))
+ (delete-region (point) (search-forward ":" nil t 2))
+ (delete-region (1- (point-max)) (point-max))
+ (when (re-search-forward "\n[ \t]*\n" nil t)
+ (user-error "Inline definitions cannot contain blank lines"))
+ ;; If footnote reference belongs to a table, make sure to
+ ;; remove any newline characters in order to preserve
+ ;; table's structure.
+ (when (org-element-lineage definition '(table-cell))
+ (while (search-forward "\n" nil t) (replace-match "")))))
+ contents
+ 'remote))
+ ;; Report success.
+ t))
+
+(defun org-edit-table.el ()
+ "Edit \"table.el\" table at point.
+\\
+A new buffer is created and the table is copied into it. Then
+the table is recognized with `table-recognize'. When done
+editing, exit with `\\[org-edit-src-exit]'. The edited text will \
+then replace
+the area in the Org mode buffer.
+
+Throw an error when not at such a table."
+ (interactive)
+ (let ((element (org-element-at-point)))
+ (unless (and (eq (org-element-type element) 'table)
+ (eq (org-element-property :type element) 'table.el)
+ (org-src--on-datum-p element))
+ (user-error "Not in a table.el table"))
+ (org-src--edit-element
+ element
+ (org-src--construct-edit-buffer-name (buffer-name) "Table")
+ #'text-mode t)
+ (when (bound-and-true-p flyspell-mode) (flyspell-mode -1))
+ (table-recognize)
+ t))
+
+(defun org-edit-export-block ()
+ "Edit export block at point.
+\\
+A new buffer is created and the block is copied into it, and the
+buffer is switched into an appropriate major mode. See also
+`org-src-lang-modes'.
+
+When done, exit with `\\[org-edit-src-exit]'. The edited text \
+will then replace
+the area in the Org mode buffer.
+
+Throw an error when not at an export block."
+ (interactive)
+ (let ((element (org-element-at-point)))
+ (unless (and (eq (org-element-type element) 'export-block)
+ (org-src--on-datum-p element))
+ (user-error "Not in an export block"))
+ (let* ((type (downcase (org-element-property :type element)))
+ (mode (org-src--get-lang-mode type)))
+ (unless (functionp mode) (error "No such language mode: %s" mode))
+ (org-src--edit-element
+ element
+ (org-src--construct-edit-buffer-name (buffer-name) type)
+ mode
+ (lambda () (org-escape-code-in-region (point-min) (point-max)))))
+ t))
+
+(defun org-edit-src-code (&optional code edit-buffer-name)
+ "Edit the source or example block at point.
+\\
+The code is copied to a separate buffer and the appropriate mode
+is turned on. When done, exit with `\\[org-edit-src-exit]'. This \
+will remove the
+original code in the Org buffer, and replace it with the edited
+version. See `org-src-window-setup' to configure the display of
+windows containing the Org buffer and the code buffer.
-(defun org-src-native-tab-command-maybe ()
- "Perform language-specific TAB action.
-Alter code block according to what TAB does in the language major mode."
- (and org-src-tab-acts-natively
- (org-in-src-block-p)
- (not (equal this-command 'org-shifttab))
- (let ((org-src-strip-leading-and-trailing-blank-lines nil))
- (org-babel-do-key-sequence-in-edit-buffer (kbd "TAB")))))
+When optional argument CODE is a string, edit it in a dedicated
+buffer instead.
-(add-hook 'org-tab-first-hook 'org-src-native-tab-command-maybe)
+When optional argument EDIT-BUFFER-NAME is non-nil, use it as the
+name of the sub-editing buffer."
+ (interactive)
+ (let* ((element (org-element-at-point))
+ (type (org-element-type element)))
+ (unless (and (memq type '(example-block src-block))
+ (org-src--on-datum-p element))
+ (user-error "Not in a source or example block"))
+ (let* ((lang
+ (if (eq type 'src-block) (org-element-property :language element)
+ "example"))
+ (lang-f (and (eq type 'src-block) (org-src--get-lang-mode lang)))
+ (babel-info (and (eq type 'src-block)
+ (org-babel-get-src-block-info 'light)))
+ deactivate-mark)
+ (when (and (eq type 'src-block) (not (functionp lang-f)))
+ (error "No such language mode: %s" lang-f))
+ (org-src--edit-element
+ element
+ (or edit-buffer-name
+ (org-src--construct-edit-buffer-name (buffer-name) lang))
+ lang-f
+ (and (null code)
+ (lambda () (org-escape-code-in-region (point-min) (point-max))))
+ (and code (org-unescape-code-in-string code)))
+ ;; Finalize buffer.
+ (setq-local org-coderef-label-format
+ (or (org-element-property :label-fmt element)
+ org-coderef-label-format))
+ (when (eq type 'src-block)
+ (setq-local org-src--babel-info babel-info)
+ (let ((edit-prep-func (intern (concat "org-babel-edit-prep:" lang))))
+ (when (fboundp edit-prep-func)
+ (funcall edit-prep-func babel-info))))
+ t)))
-(defun org-src-font-lock-fontify-block (lang start end)
- "Fontify code block.
-This function is called by emacs automatic fontification, as long
-as `org-src-fontify-natively' is non-nil. For manual
-fontification of code blocks see `org-src-fontify-block' and
-`org-src-fontify-buffer'"
- (let ((lang-mode (org-src-get-lang-mode lang)))
- (if (fboundp lang-mode)
- (let ((string (buffer-substring-no-properties start end))
- (modified (buffer-modified-p))
- (org-buffer (current-buffer)) pos next)
- (remove-text-properties start end '(face nil))
- (with-current-buffer
- (get-buffer-create
- (concat " org-src-fontification:" (symbol-name lang-mode)))
- (delete-region (point-min) (point-max))
- (insert string " ") ;; so there's a final property change
- (unless (eq major-mode lang-mode) (funcall lang-mode))
- (org-font-lock-ensure)
- (setq pos (point-min))
- (while (setq next (next-single-property-change pos 'face))
- (put-text-property
- (+ start (1- pos)) (1- (+ start next)) 'face
- (get-text-property pos 'face) org-buffer)
- (setq pos next)))
- (add-text-properties
- start end
- '(font-lock-fontified t fontified t font-lock-multiline t))
- (set-buffer-modified-p modified)))))
+(defun org-edit-inline-src-code ()
+ "Edit inline source code at point."
+ (interactive)
+ (let ((context (org-element-context)))
+ (unless (and (eq (org-element-type context) 'inline-src-block)
+ (org-src--on-datum-p context))
+ (user-error "Not on inline source code"))
+ (let* ((lang (org-element-property :language context))
+ (lang-f (org-src--get-lang-mode lang))
+ (babel-info (org-babel-get-src-block-info 'light))
+ deactivate-mark)
+ (unless (functionp lang-f) (error "No such language mode: %s" lang-f))
+ (org-src--edit-element
+ context
+ (org-src--construct-edit-buffer-name (buffer-name) lang)
+ lang-f
+ (lambda ()
+ ;; Inline src blocks are limited to one line.
+ (while (re-search-forward "\n[ \t]*" nil t) (replace-match " "))
+ ;; Trim contents.
+ (goto-char (point-min))
+ (skip-chars-forward " \t")
+ (delete-region (point-min) (point))
+ (goto-char (point-max))
+ (skip-chars-backward " \t")
+ (delete-region (point) (point-max))))
+ ;; Finalize buffer.
+ (setq-local org-src--babel-info babel-info)
+ (setq-local org-src--preserve-indentation t)
+ (let ((edit-prep-func (intern (concat "org-babel-edit-prep:" lang))))
+ (when (fboundp edit-prep-func) (funcall edit-prep-func babel-info)))
+ ;; Return success.
+ t)))
-(defvar org-src-fontify-natively)
+(defun org-edit-fixed-width-region ()
+ "Edit the fixed-width ASCII drawing at point.
+\\
+This must be a region where each line starts with a colon
+followed by a space or a newline character.
+
+A new buffer is created and the fixed-width region is copied into
+it, and the buffer is switched into the major mode defined in
+`org-edit-fixed-width-region-mode', which see.
+
+When done, exit with `\\[org-edit-src-exit]'. The edited text \
+will then replace
+the area in the Org mode buffer."
+ (interactive)
+ (let ((element (org-element-at-point)))
+ (unless (and (eq (org-element-type element) 'fixed-width)
+ (org-src--on-datum-p element))
+ (user-error "Not in a fixed-width area"))
+ (org-src--edit-element
+ element
+ (org-src--construct-edit-buffer-name (buffer-name) "Fixed Width")
+ org-edit-fixed-width-region-mode
+ (lambda () (while (not (eobp)) (insert ": ") (forward-line))))
+ ;; Return success.
+ t))
-(defun org-src-fontify-block ()
- "Fontify code block at point."
+(defun org-edit-src-abort ()
+ "Abort editing of the src code and return to the Org buffer."
(interactive)
- (save-excursion
- (let ((org-src-fontify-natively t)
- (info (org-edit-src-find-region-and-lang)))
- (font-lock-fontify-region (nth 0 info) (nth 1 info)))))
+ (let (org-src--allow-write-back) (org-edit-src-exit)))
-(defun org-src-fontify-buffer ()
- "Fontify all code blocks in the current buffer."
+(defun org-edit-src-continue (e)
+ "Unconditionally return to buffer editing area under point.
+Throw an error if there is no such buffer."
+ (interactive "e")
+ (mouse-set-point e)
+ (let ((buf (get-char-property (point) 'edit-buffer)))
+ (if buf (org-src-switch-to-buffer buf 'continue)
+ (user-error "No sub-editing buffer for area at point"))))
+
+(defun org-edit-src-save ()
+ "Save parent buffer with current state source-code buffer."
(interactive)
- (org-babel-map-src-blocks nil
- (org-src-fontify-block)))
+ (unless (org-src-edit-buffer-p) (user-error "Not in a sub-editing buffer"))
+ (set-buffer-modified-p nil)
+ (let ((edited-code (org-src--contents-for-write-back))
+ (beg org-src--beg-marker)
+ (end org-src--end-marker)
+ (overlay org-src--overlay))
+ (with-current-buffer (org-src--source-buffer)
+ (undo-boundary)
+ (goto-char beg)
+ ;; Temporarily disable read-only features of OVERLAY in order to
+ ;; insert new contents.
+ (delete-overlay overlay)
+ (delete-region beg end)
+ (let ((expecting-bol (bolp)))
+ (insert edited-code)
+ (when (and expecting-bol (not (bolp))) (insert "\n")))
+ (save-buffer)
+ (move-overlay overlay beg (point))))
+ ;; `write-contents-functions' requires the function to return
+ ;; a non-nil value so that other functions are not called.
+ t)
+
+(defun org-edit-src-exit ()
+ "Kill current sub-editing buffer and return to source buffer."
+ (interactive)
+ (unless (org-src-edit-buffer-p) (error "Not in a sub-editing buffer"))
+ (let* ((beg org-src--beg-marker)
+ (end org-src--end-marker)
+ (write-back org-src--allow-write-back)
+ (remote org-src--remote)
+ (coordinates (and (not remote)
+ (org-src--coordinates (point) 1 (point-max))))
+ (code (and write-back (org-src--contents-for-write-back))))
+ (set-buffer-modified-p nil)
+ ;; Switch to source buffer. Kill sub-editing buffer.
+ (let ((edit-buffer (current-buffer))
+ (source-buffer (marker-buffer beg)))
+ (unless source-buffer (error "Source buffer disappeared. Aborting"))
+ (org-src-switch-to-buffer source-buffer 'exit)
+ (kill-buffer edit-buffer))
+ ;; Insert modified code. Ensure it ends with a newline character.
+ (org-with-wide-buffer
+ (when (and write-back (not (equal (buffer-substring beg end) code)))
+ (undo-boundary)
+ (goto-char beg)
+ (delete-region beg end)
+ (let ((expecting-bol (bolp)))
+ (insert code)
+ (when (and expecting-bol (not (bolp))) (insert "\n")))))
+ ;; If we are to return to source buffer, put point at an
+ ;; appropriate location. In particular, if block is hidden, move
+ ;; to the beginning of the block opening line.
+ (unless remote
+ (goto-char beg)
+ (cond
+ ;; Block is hidden; move at start of block.
+ ((cl-some (lambda (o) (eq (overlay-get o 'invisible) 'org-hide-block))
+ (overlays-at (point)))
+ (beginning-of-line 0))
+ (write-back (org-src--goto-coordinates coordinates beg end))))
+ ;; Clean up left-over markers and restore window configuration.
+ (set-marker beg nil)
+ (set-marker end nil)
+ (when org-src--saved-temp-window-config
+ (set-window-configuration org-src--saved-temp-window-config)
+ (setq org-src--saved-temp-window-config nil))))
-(defun org-src-get-lang-mode (lang)
- "Return major mode that should be used for LANG.
-LANG is a string, and the returned major mode is a symbol."
- (intern
- (concat
- (let ((l (or (cdr (assoc lang org-src-lang-modes)) lang)))
- (if (symbolp l) (symbol-name l) l))
- "-mode")))
(provide 'org-src)
diff --git a/lisp/org/org-table.el b/lisp/org/org-table.el
index 08bbf3277ba..357fdcfa441 100644
--- a/lisp/org/org-table.el
+++ b/lisp/org/org-table.el
@@ -1,4 +1,4 @@
-;;; org-table.el --- The table editor for Org-mode
+;;; org-table.el --- The Table Editor for Org -*- lexical-binding: t; -*-
;; Copyright (C) 2004-2017 Free Software Foundation, Inc.
@@ -24,27 +24,53 @@
;;
;;; Commentary:
-;; This file contains the table editor and spreadsheet for Org-mode.
+;; This file contains the table editor and spreadsheet for Org mode.
;; Watch out: Here we are talking about two different kind of tables.
-;; Most of the code is for the tables created with the Org-mode table editor.
+;; Most of the code is for the tables created with the Org mode table editor.
;; Sometimes, we talk about tables created and edited with the table.el
;; Emacs package. We call the former org-type tables, and the latter
;; table.el-type tables.
;;; Code:
-(eval-when-compile
- (require 'cl))
+(require 'cl-lib)
(require 'org)
-(declare-function org-export-string-as "ox"
- (string backend &optional body-only ext-plist))
-(declare-function aa2u "ext:ascii-art-to-unicode" ())
+(declare-function org-element-at-point "org-element" ())
+(declare-function org-element-contents "org-element" (element))
+(declare-function org-element-extract-element "org-element" (element))
+(declare-function org-element-interpret-data "org-element" (data))
+(declare-function org-element-lineage "org-element"
+ (blob &optional types with-self))
+(declare-function org-element-map "org-element"
+ (data types fun
+ &optional info first-match no-recursion with-affiliated))
+(declare-function org-element-parse-buffer "org-element"
+ (&optional granularity visible-only))
+(declare-function org-element-property "org-element" (property element))
+(declare-function org-element-type "org-element" (element))
+
+(declare-function org-export-create-backend "ox" (&rest rest) t)
+(declare-function org-export-data-with-backend "ox" (data backend info))
+(declare-function org-export-filter-apply-functions "ox"
+ (filters value info))
+(declare-function org-export-first-sibling-p "ox" (blob info))
+(declare-function org-export-get-backend "ox" (name))
+(declare-function org-export-get-environment "ox"
+ (&optional backend subtreep ext-plist))
+(declare-function org-export-install-filters "ox" (info))
+(declare-function org-export-table-has-special-column-p "ox" (table))
+(declare-function org-export-table-row-is-special-p "ox" (table-row info))
+
+(declare-function calc-eval "calc" (str &optional separator &rest args))
+
(defvar orgtbl-mode) ; defined below
(defvar orgtbl-mode-menu) ; defined when orgtbl mode get initialized
(defvar constants-unit-system)
+(defvar org-export-filters-alist)
(defvar org-table-follow-field-mode)
+(defvar sort-fold-case)
(defvar orgtbl-after-send-table-hook nil
"Hook for functions attaching to `C-c C-c', if the table is sent.
@@ -52,7 +78,7 @@ This can be used to add additional functionality after the table is sent
to the receiver position, otherwise, if table is not sent, the functions
are not run.")
-(defvar org-table-TBLFM-begin-regexp "|\n[ \t]*#\\+TBLFM: ")
+(defvar org-table-TBLFM-begin-regexp "^[ \t]*|.*\n[ \t]*#\\+TBLFM: ")
(defcustom orgtbl-optimized (eq org-enable-table-editor 'optimized)
"Non-nil means use the optimized table editor version for `orgtbl-mode'.
@@ -63,7 +89,7 @@ for empty fields). Outside tables, the correct binding of the keys is
restored.
The default for this option is t if the optimized version is also used in
-Org-mode. See the variable `org-enable-table-editor' for details. Changing
+Org mode. See the variable `org-enable-table-editor' for details. Changing
this variable requires a restart of Emacs to become effective."
:group 'org-table
:type 'boolean)
@@ -118,7 +144,7 @@ table, obtained by prompting the user."
(string :tag "Format"))))
(defgroup org-table-settings nil
- "Settings for tables in Org-mode."
+ "Settings for tables in Org mode."
:tag "Org Table Settings"
:group 'org-table)
@@ -167,13 +193,13 @@ alignment to the right border applies."
:type 'number)
(defgroup org-table-editing nil
- "Behavior of tables during editing in Org-mode."
+ "Behavior of tables during editing in Org mode."
:tag "Org Table Editing"
:group 'org-table)
(defcustom org-table-automatic-realign t
"Non-nil means automatically re-align table when pressing TAB or RETURN.
-When nil, aligning is only done with \\[org-table-align], or after column
+When nil, aligning is only done with `\\[org-table-align]', or after column
removal/insertion."
:group 'org-table-editing
:type 'boolean)
@@ -219,12 +245,12 @@ this line."
:type 'boolean)
(defgroup org-table-calculation nil
- "Options concerning tables in Org-mode."
+ "Options concerning tables in Org mode."
:tag "Org Table Calculation"
:group 'org-table)
(defcustom org-table-use-standard-references 'from
- "Should org-mode work with table references like B3 instead of @3$2?
+ "Non-nil means using table references like B3 instead of @3$2.
Possible values are:
nil never use them
from accept as input, do not present for editing
@@ -236,9 +262,15 @@ t accept as input and present for editing"
(const :tag "Convert user input, don't offer during editing" from)))
(defcustom org-table-copy-increment t
- "Non-nil means increment when copying current field with \\[org-table-copy-down]."
+ "Non-nil means increment when copying current field with \
+`\\[org-table-copy-down]'."
:group 'org-table-calculation
- :type 'boolean)
+ :version "26.1"
+ :package-version '(Org . "8.3")
+ :type '(choice
+ (const :tag "Use the difference between the current and the above fields" t)
+ (integer :tag "Use a number" 1)
+ (const :tag "Don't increment the value when copying a field" nil)))
(defcustom org-calc-default-modes
'(calc-internal-prec 12
@@ -251,16 +283,16 @@ t accept as input and present for editing"
)
"List with Calc mode settings for use in `calc-eval' for table formulas.
The list must contain alternating symbols (Calc modes variables and values).
-Don't remove any of the default settings, just change the values. Org-mode
+Don't remove any of the default settings, just change the values. Org mode
relies on the variables to be present in the list."
:group 'org-table-calculation
:type 'plist)
(defcustom org-table-duration-custom-format 'hours
"Format for the output of calc computations like $1+$2;t.
-The default value is 'hours, and will output the results as a
-number of hours. Other allowed values are 'seconds, 'minutes and
-'days, and the output will be a fraction of seconds, minutes or
+The default value is `hours', and will output the results as a
+number of hours. Other allowed values are `seconds', `minutes' and
+`days', and the output will be a fraction of seconds, minutes or
days."
:group 'org-table-calculation
:version "24.1"
@@ -285,7 +317,7 @@ which should be evaluated as described in the manual and in the documentation
string of the command `org-table-eval-formula'. This feature requires the
Emacs calc package.
When this variable is nil, formula calculation is only available through
-the command \\[org-table-eval-formula]."
+the command `\\[org-table-eval-formula]'."
:group 'org-table-calculation
:type 'boolean)
@@ -317,15 +349,12 @@ Constants can also be defined on a per-file basis using a line like
(defcustom org-table-allow-automatic-line-recalculation t
"Non-nil means lines marked with |#| or |*| will be recomputed automatically.
-Automatically means when TAB or RET or C-c C-c are pressed in the line."
+\\\
+Automatically means when `TAB' or `RET' or `\\[org-ctrl-c-ctrl-c]' \
+are pressed in the line."
:group 'org-table-calculation
:type 'boolean)
-(defcustom org-table-error-on-row-ref-crossing-hline t
- "OBSOLETE VARIABLE, please see `org-table-relative-ref-may-cross-hline'."
- :group 'org-table
- :type 'boolean)
-
(defcustom org-table-relative-ref-may-cross-hline t
"Non-nil means relative formula references may cross hlines.
Here are the allowed values:
@@ -345,8 +374,20 @@ portability of tables."
(const :tag "Stick to hline" nil)
(const :tag "Error on attempt to cross" error)))
+(defcustom org-table-formula-create-columns nil
+ "Non-nil means that evaluation of a field formula can add new
+columns if an out-of-bounds field is being set."
+ :group 'org-table-calculation
+ :version "26.1"
+ :package-version '(Org . "8.3")
+ :type '(choice
+ (const :tag "Setting an out-of-bounds field generates an error (default)" nil)
+ (const :tag "Setting an out-of-bounds field silently adds columns as needed" t)
+ (const :tag "Setting an out-of-bounds field adds columns as needed, but issues a warning message" warn)
+ (const :tag "When setting an out-of-bounds field, the user is prompted" prompt)))
+
(defgroup org-table-import-export nil
- "Options concerning table import and export in Org-mode."
+ "Options concerning table import and export in Org mode."
:tag "Org Table Import Export"
:group 'org-table)
@@ -359,38 +400,73 @@ available parameters."
:group 'org-table-import-export
:type 'string)
+(defcustom org-table-convert-region-max-lines 999
+ "Max lines that `org-table-convert-region' will attempt to process.
+
+The function can be slow on larger regions; this safety feature
+prevents it from hanging emacs."
+ :group 'org-table-import-export
+ :type 'integer
+ :version "26.1"
+ :package-version '(Org . "8.3"))
+
(defconst org-table-auto-recalculate-regexp "^[ \t]*| *# *\\(|\\|$\\)"
- "Detects a table line marked for automatic recalculation.")
+ "Regexp matching a line marked for automatic recalculation.")
+
(defconst org-table-recalculate-regexp "^[ \t]*| *[#*] *\\(|\\|$\\)"
- "Detects a table line marked for automatic recalculation.")
+ "Regexp matching a line marked for recalculation.")
+
(defconst org-table-calculate-mark-regexp "^[ \t]*| *[!$^_#*] *\\(|\\|$\\)"
- "Detects a table line marked for automatic recalculation.")
+ "Regexp matching a line marked for calculation.")
+
(defconst org-table-border-regexp "^[ \t]*[^| \t]"
- "Searching from within a table (any type) this finds the first line outside the table.")
+ "Regexp matching any line outside an Org table.")
+
(defvar org-table-last-highlighted-reference nil)
+
(defvar org-table-formula-history nil)
(defvar org-table-column-names nil
- "Alist with column names, derived from the `!' line.")
+ "Alist with column names, derived from the `!' line.
+This variable is initialized with `org-table-analyze'.")
+
(defvar org-table-column-name-regexp nil
- "Regular expression matching the current column names.")
+ "Regular expression matching the current column names.
+This variable is initialized with `org-table-analyze'.")
+
(defvar org-table-local-parameters nil
- "Alist with parameter names, derived from the `$' line.")
+ "Alist with parameter names, derived from the `$' line.
+This variable is initialized with `org-table-analyze'.")
+
(defvar org-table-named-field-locations nil
- "Alist with locations of named fields.")
+ "Alist with locations of named fields.
+Associations follow the pattern (NAME LINE COLUMN) where
+ NAME is the name of the field as a string,
+ LINE is the number of lines from the beginning of the table,
+ COLUMN is the column of the field, as an integer.
+This variable is initialized with `org-table-analyze'.")
(defvar org-table-current-line-types nil
- "Table row types, non-nil only for the duration of a command.")
-(defvar org-table-current-begin-line nil
- "Table begin line, non-nil only for the duration of a command.")
+ "Table row types in current table.
+This variable is initialized with `org-table-analyze'.")
+
(defvar org-table-current-begin-pos nil
- "Table begin position, non-nil only for the duration of a command.")
+ "Current table begin position, as a marker.
+This variable is initialized with `org-table-analyze'.")
+
(defvar org-table-current-ncol nil
- "Number of columns in table, non-nil only for the duration of a command.")
+ "Number of columns in current table.
+This variable is initialized with `org-table-analyze'.")
+
(defvar org-table-dlines nil
- "Vector of data line line numbers in the current table.")
+ "Vector of data line line numbers in the current table.
+Line numbers are counted from the beginning of the table. This
+variable is initialized with `org-table-analyze'.")
+
(defvar org-table-hlines nil
- "Vector of hline line numbers in the current table.")
+ "Vector of hline line numbers in the current table.
+Line numbers are counted from the beginning of the table. This
+variable is initialized with `org-table-analyze'.")
(defconst org-table-range-regexp
"@\\([-+]?I*[-+]?[0-9]*\\)?\\(\\$[-+]?[0-9]+\\)?\\(\\.\\.@?\\([-+]?I*[-+]?[0-9]*\\)?\\(\\$[-+]?[0-9]+\\)?\\)?"
@@ -404,85 +480,33 @@ available parameters."
"\\(" "@?[-0-9I$&]+" "\\|" "[a-zA-Z]\\{1,2\\}\\([0-9]+\\|&\\)" "\\|" "\\$[a-zA-Z0-9]+" "\\)")
"Match a range for reference display.")
-(defun org-table-colgroup-line-p (line)
- "Is this a table line colgroup information?"
- (save-match-data
- (and (string-match "[<>]\\|&[lg]t;" line)
- (string-match "\\`[ \t]*|[ \t]*/[ \t]*\\(|[ \t<>0-9|lgt&;]+\\)\\'"
- line)
- (not (delq
- nil
- (mapcar
- (lambda (s)
- (not (member s '("" "<" ">" "<>" "<" ">" "<>"))))
- (org-split-string (match-string 1 line) "[ \t]*|[ \t]*")))))))
-
-(defun org-table-cookie-line-p (line)
- "Is this a table line with only alignment/width cookies?"
- (save-match-data
- (and (string-match "[<>]\\|&[lg]t;" line)
- (or (string-match
- "\\`[ \t]*|[ \t]*/[ \t]*\\(|[ \t<>0-9|lrcgt&;]+\\)\\'" line)
- (string-match "\\(\\`[ \t<>lrc0-9|gt&;]+\\'\\)" line))
- (not (delq nil (mapcar
- (lambda (s)
- (not (or (equal s "")
- (string-match
- "\\`<\\([lrc]?[0-9]+\\|[lrc]\\)>\\'" s)
- (string-match
- "\\`<\\([lrc]?[0-9]+\\|[lrc]\\)>\\'"
- s))))
- (org-split-string (match-string 1 line)
- "[ \t]*|[ \t]*")))))))
-
-(defvar org-table-clean-did-remove-column nil) ; dynamically scoped
-(defun org-table-clean-before-export (lines &optional maybe-quoted)
- "Check if the table has a marking column.
-If yes remove the column and the special lines."
- (let ((special (if maybe-quoted
- "^[ \t]*| *\\\\?[#!$*_^/ ] *|"
- "^[ \t]*| *[#!$*_^/ ] *|"))
- (ignore (if maybe-quoted
- "^[ \t]*| *\\\\?[!$_^/] *|"
- "^[ \t]*| *[!$_^/] *|")))
- (setq org-table-clean-did-remove-column
- (not (memq nil
- (mapcar
- (lambda (line)
- (or (string-match org-table-hline-regexp line)
- (string-match special line)))
- lines))))
- (delq nil
- (mapcar
- (lambda (line)
- (cond
- ((or (org-table-colgroup-line-p line) ;; colgroup info
- (org-table-cookie-line-p line) ;; formatting cookies
- (and org-table-clean-did-remove-column
- (string-match ignore line))) ;; non-exportable data
- nil)
- ((and org-table-clean-did-remove-column
- (or (string-match "^\\([ \t]*\\)|-+\\+" line)
- (string-match "^\\([ \t]*\\)|[^|]*|" line)))
- ;; remove the first column
- (replace-match "\\1|" t nil line))
- (t line)))
- lines))))
-
(defconst org-table-translate-regexp
(concat "\\(" "@[-0-9I$]+" "\\|" "[a-zA-Z]\\{1,2\\}\\([0-9]+\\|&\\)" "\\)")
"Match a reference that needs translation, for reference display.")
+(defmacro org-table-save-field (&rest body)
+ "Save current field; execute BODY; restore field.
+Field is restored even in case of abnormal exit."
+ (declare (debug (body)))
+ (org-with-gensyms (line column)
+ `(let ((,line (copy-marker (line-beginning-position)))
+ (,column (org-table-current-column)))
+ (unwind-protect
+ (progn ,@body)
+ (goto-char ,line)
+ (org-table-goto-column ,column)
+ (set-marker ,line nil)))))
+
;;;###autoload
(defun org-table-create-with-table.el ()
"Use the table.el package to insert a new table.
-If there is already a table at point, convert between Org-mode tables
+If there is already a table at point, convert between Org tables
and table.el tables."
(interactive)
(require 'table)
(cond
((org-at-table.el-p)
- (if (y-or-n-p "Convert table to Org-mode table? ")
+ (if (y-or-n-p "Convert table to Org table? ")
(org-table-convert)))
((org-at-table-p)
(when (y-or-n-p "Convert table to table.el table? ")
@@ -526,7 +550,7 @@ SIZE is a string Columns x Rows like for example \"3x2\"."
(beginning-of-line 1)
(newline))
;; (mapcar (lambda (x) (insert line)) (make-list rows t))
- (dotimes (i rows) (insert line))
+ (dotimes (_ rows) (insert line))
(goto-char pos)
(if (> rows 1)
;; Insert a hline after the first row.
@@ -539,15 +563,18 @@ SIZE is a string Columns x Rows like for example \"3x2\"."
;;;###autoload
(defun org-table-convert-region (beg0 end0 &optional separator)
"Convert region to a table.
+
The region goes from BEG0 to END0, but these borders will be moved
slightly, to make sure a beginning of line in the first line is included.
SEPARATOR specifies the field separator in the lines. It can have the
following values:
-(4) Use the comma as a field separator
-(16) Use a TAB as field separator
-integer When a number, use that many spaces as field separator
+(4) Use the comma as a field separator
+(16) Use a TAB as field separator
+(64) Prompt for a regular expression as field separator
+integer When a number, use that many spaces, or a TAB, as field separator
+regexp When a regular expression, use it to match the separator
nil When nil, the command tries to be smart and figure out the
separator in the following way:
- when each line contains a TAB, assume TAB-separated material
@@ -557,45 +584,52 @@ nil When nil, the command tries to be smart and figure out the
(let* ((beg (min beg0 end0))
(end (max beg0 end0))
re)
- (goto-char beg)
- (beginning-of-line 1)
- (setq beg (point-marker))
- (goto-char end)
- (if (bolp) (backward-char 1) (end-of-line 1))
- (setq end (point-marker))
- ;; Get the right field separator
- (unless separator
+ (if (> (count-lines beg end) org-table-convert-region-max-lines)
+ (user-error "Region is longer than `org-table-convert-region-max-lines' (%s) lines; not converting"
+ org-table-convert-region-max-lines)
+ (if (equal separator '(64))
+ (setq separator (read-regexp "Regexp for field separator")))
(goto-char beg)
- (setq separator
+ (beginning-of-line 1)
+ (setq beg (point-marker))
+ (goto-char end)
+ (if (bolp) (backward-char 1) (end-of-line 1))
+ (setq end (point-marker))
+ ;; Get the right field separator
+ (unless separator
+ (goto-char beg)
+ (setq separator
+ (cond
+ ((not (re-search-forward "^[^\n\t]+$" end t)) '(16))
+ ((not (re-search-forward "^[^\n,]+$" end t)) '(4))
+ (t 1))))
+ (goto-char beg)
+ (if (equal separator '(4))
+ (while (< (point) end)
+ ;; parse the csv stuff
(cond
- ((not (re-search-forward "^[^\n\t]+$" end t)) '(16))
- ((not (re-search-forward "^[^\n,]+$" end t)) '(4))
- (t 1))))
- (goto-char beg)
- (if (equal separator '(4))
- (while (< (point) end)
- ;; parse the csv stuff
- (cond
- ((looking-at "^") (insert "| "))
- ((looking-at "[ \t]*$") (replace-match " |") (beginning-of-line 2))
- ((looking-at "[ \t]*\"\\([^\"\n]*\\)\"")
- (replace-match "\\1")
- (if (looking-at "\"") (insert "\"")))
- ((looking-at "[^,\n]+") (goto-char (match-end 0)))
- ((looking-at "[ \t]*,") (replace-match " | "))
- (t (beginning-of-line 2))))
- (setq re (cond
- ((equal separator '(4)) "^\\|\"?[ \t]*,[ \t]*\"?")
- ((equal separator '(16)) "^\\|\t")
- ((integerp separator)
- (if (< separator 1)
- (user-error "Number of spaces in separator must be >= 1")
- (format "^ *\\| *\t *\\| \\{%d,\\}" separator)))
- (t (error "This should not happen"))))
- (while (re-search-forward re end t)
- (replace-match "| " t t)))
- (goto-char beg)
- (org-table-align)))
+ ((looking-at "^") (insert "| "))
+ ((looking-at "[ \t]*$") (replace-match " |") (beginning-of-line 2))
+ ((looking-at "[ \t]*\"\\([^\"\n]*\\)\"")
+ (replace-match "\\1")
+ (if (looking-at "\"") (insert "\"")))
+ ((looking-at "[^,\n]+") (goto-char (match-end 0)))
+ ((looking-at "[ \t]*,") (replace-match " | "))
+ (t (beginning-of-line 2))))
+ (setq re (cond
+ ((equal separator '(4)) "^\\|\"?[ \t]*,[ \t]*\"?")
+ ((equal separator '(16)) "^\\|\t")
+ ((integerp separator)
+ (if (< separator 1)
+ (user-error "Number of spaces in separator must be >= 1")
+ (format "^ *\\| *\t *\\| \\{%d,\\}" separator)))
+ ((stringp separator)
+ (format "^ *\\|%s" separator))
+ (t (error "This should not happen"))))
+ (while (re-search-forward re end t)
+ (replace-match "| " t t)))
+ (goto-char beg)
+ (org-table-align))))
;;;###autoload
(defun org-table-import (file arg)
@@ -611,8 +645,6 @@ are found, lines will be split on whitespace into fields."
(org-table-convert-region beg (+ (point) (- (point-max) pm)) arg)))
-(defvar org-table-last-alignment)
-(defvar org-table-last-column-widths)
;;;###autoload
(defun org-table-export (&optional file format)
"Export table to a file, with configurable format.
@@ -630,77 +662,61 @@ extension of the given file name, and finally on the variable
`org-table-export-default-format'."
(interactive)
(unless (org-at-table-p) (user-error "No table at point"))
- (org-table-align) ;; make sure we have everything we need
- (let* ((beg (org-table-begin))
- (end (org-table-end))
- (txt (buffer-substring-no-properties beg end))
- (file (or file (org-entry-get beg "TABLE_EXPORT_FILE" t)))
- (formats '("orgtbl-to-tsv" "orgtbl-to-csv"
- "orgtbl-to-latex" "orgtbl-to-html"
- "orgtbl-to-generic" "orgtbl-to-texinfo"
- "orgtbl-to-orgtbl"))
- (format (or format
- (org-entry-get beg "TABLE_EXPORT_FORMAT" t)))
- buf deffmt-readable fileext)
+ (org-table-align) ; Make sure we have everything we need.
+ (let ((file (or file (org-entry-get (point) "TABLE_EXPORT_FILE" t))))
(unless file
(setq file (read-file-name "Export table to: "))
(unless (or (not (file-exists-p file))
(y-or-n-p (format "Overwrite file %s? " file)))
(user-error "File not written")))
- (if (file-directory-p file)
- (user-error "This is a directory path, not a file"))
- (if (and (buffer-file-name)
- (equal (file-truename file)
- (file-truename (buffer-file-name))))
- (user-error "Please specify a file name that is different from current"))
- (setq fileext (concat (file-name-extension file) "$"))
- (unless format
- (setq deffmt-readable
- (or (car (delq nil (mapcar (lambda(f) (if (string-match fileext f) f)) formats)))
- org-table-export-default-format))
- (while (string-match "\t" deffmt-readable)
- (setq deffmt-readable (replace-match "\\t" t t deffmt-readable)))
- (while (string-match "\n" deffmt-readable)
- (setq deffmt-readable (replace-match "\\n" t t deffmt-readable)))
- (setq format (org-completing-read "Format: " formats nil nil deffmt-readable)))
- (if (string-match "\\([^ \t\r\n]+\\)\\( +.*\\)?" format)
- (let* ((transform (intern (match-string 1 format)))
- (params (if (match-end 2)
- (read (concat "(" (match-string 2 format) ")"))))
- (skip (plist-get params :skip))
- (skipcols (plist-get params :skipcols))
- (lines (nthcdr (or skip 0) (org-split-string txt "[ \t]*\n[ \t]*")))
- (lines (org-table-clean-before-export lines))
- (i0 (if org-table-clean-did-remove-column 2 1))
- (table (mapcar
- (lambda (x)
- (if (string-match org-table-hline-regexp x)
- 'hline
- (org-remove-by-index
- (org-split-string (org-trim x) "\\s-*|\\s-*")
- skipcols i0)))
- lines))
- (fun (if (= i0 2) 'cdr 'identity))
- (org-table-last-alignment
- (org-remove-by-index (funcall fun org-table-last-alignment)
- skipcols i0))
- (org-table-last-column-widths
- (org-remove-by-index (funcall fun org-table-last-column-widths)
- skipcols i0)))
-
- (unless (fboundp transform)
- (user-error "No such transformation function %s" transform))
- (setq txt (funcall transform table params))
-
- (with-current-buffer (find-file-noselect file)
- (setq buf (current-buffer))
- (erase-buffer)
- (fundamental-mode)
- (insert txt "\n")
- (save-buffer))
- (kill-buffer buf)
- (message "Export done."))
- (user-error "TABLE_EXPORT_FORMAT invalid"))))
+ (when (file-directory-p file)
+ (user-error "This is a directory path, not a file"))
+ (when (and (buffer-file-name (buffer-base-buffer))
+ (file-equal-p
+ (file-truename file)
+ (file-truename (buffer-file-name (buffer-base-buffer)))))
+ (user-error "Please specify a file name that is different from current"))
+ (let ((fileext (concat (file-name-extension file) "$"))
+ (format (or format (org-entry-get (point) "TABLE_EXPORT_FORMAT" t))))
+ (unless format
+ (let* ((formats '("orgtbl-to-tsv" "orgtbl-to-csv" "orgtbl-to-latex"
+ "orgtbl-to-html" "orgtbl-to-generic"
+ "orgtbl-to-texinfo" "orgtbl-to-orgtbl"
+ "orgtbl-to-unicode"))
+ (deffmt-readable
+ (replace-regexp-in-string
+ "\t" "\\t"
+ (replace-regexp-in-string
+ "\n" "\\n"
+ (or (car (delq nil
+ (mapcar
+ (lambda (f)
+ (and (string-match-p fileext f) f))
+ formats)))
+ org-table-export-default-format)
+ t t) t t)))
+ (setq format
+ (org-completing-read
+ "Format: " formats nil nil deffmt-readable))))
+ (if (string-match "\\([^ \t\r\n]+\\)\\( +.*\\)?" format)
+ (let ((transform (intern (match-string 1 format)))
+ (params (and (match-end 2)
+ (read (concat "(" (match-string 2 format) ")"))))
+ (table (org-table-to-lisp
+ (buffer-substring-no-properties
+ (org-table-begin) (org-table-end)))))
+ (unless (fboundp transform)
+ (user-error "No such transformation function %s" transform))
+ (let (buf)
+ (with-current-buffer (find-file-noselect file)
+ (setq buf (current-buffer))
+ (erase-buffer)
+ (fundamental-mode)
+ (insert (funcall transform table params) "\n")
+ (save-buffer))
+ (kill-buffer buf))
+ (message "Export done."))
+ (user-error "TABLE_EXPORT_FORMAT invalid")))))
(defvar org-table-aligned-begin-marker (make-marker)
"Marker at the beginning of the table last aligned.
@@ -714,13 +730,11 @@ This is being used to correctly align a single field after TAB or RET.")
(defvar org-table-last-column-widths nil
"List of max width of fields in each column.
This is being used to correctly align a single field after TAB or RET.")
-(defvar org-table-formula-debug nil
+(defvar-local org-table-formula-debug nil
"Non-nil means debug table formulas.
When nil, simply write \"#ERROR\" in corrupted fields.")
-(make-variable-buffer-local 'org-table-formula-debug)
-(defvar org-table-overlay-coordinates nil
+(defvar-local org-table-overlay-coordinates nil
"Overlay coordinates after each align of a table.")
-(make-variable-buffer-local 'org-table-overlay-coordinates)
(defvar org-last-recalc-line nil)
(defvar org-table-do-narrow t) ; for dynamic scoping
@@ -731,216 +745,198 @@ When nil, simply write \"#ERROR\" in corrupted fields.")
(defun org-table-align ()
"Align the table at point by aligning all vertical bars."
(interactive)
- (let* (
- ;; Limits of table
- (beg (org-table-begin))
- (end (org-table-end))
- ;; Current cursor position
- (linepos (org-current-line))
- (colpos (org-table-current-column))
- (winstart (window-start))
- (winstartline (org-current-line (min winstart (1- (point-max)))))
- lines (new "") lengths l typenums ty fields maxfields i
- column
- (indent "") cnt frac
- rfmt hfmt
- (spaces '(1 . 1))
- (sp1 (car spaces))
- (sp2 (cdr spaces))
- (rfmt1 (concat
- (make-string sp2 ?\ ) "%%%s%ds" (make-string sp1 ?\ ) "|"))
- (hfmt1 (concat
- (make-string sp2 ?-) "%s" (make-string sp1 ?-) "+"))
- emptystrings links dates emph raise narrow
- falign falign1 fmax f1 len c e space)
- (untabify beg end)
- (remove-text-properties beg end '(org-cwidth t org-dwidth t display t))
- ;; Check if we have links or dates
- (goto-char beg)
- (setq links (re-search-forward org-bracket-link-regexp end t))
- (goto-char beg)
- (setq emph (and org-hide-emphasis-markers
- (re-search-forward org-emph-re end t)))
- (goto-char beg)
- (setq raise (and org-use-sub-superscripts
- (re-search-forward org-match-substring-regexp end t)))
- (goto-char beg)
- (setq dates (and org-display-custom-times
- (re-search-forward org-ts-regexp-both end t)))
- ;; Make sure the link properties are right
- (when links (goto-char beg) (while (org-activate-bracket-links end)))
- ;; Make sure the date properties are right
- (when dates (goto-char beg) (while (org-activate-dates end)))
- (when emph (goto-char beg) (while (org-do-emphasis-faces end)))
- (when raise (goto-char beg) (while (org-raise-scripts end)))
-
- ;; Check if we are narrowing any columns
- (goto-char beg)
- (setq narrow (and org-table-do-narrow
- org-format-transports-properties-p
- (re-search-forward "<[lrc]?[0-9]+>" end t)))
- (goto-char beg)
- (setq falign (re-search-forward "<[lrc][0-9]*>" end t))
- (goto-char beg)
- ;; Get the rows
- (setq lines (org-split-string
- (buffer-substring beg end) "\n"))
- ;; Store the indentation of the first line
- (if (string-match "^ *" (car lines))
- (setq indent (make-string (- (match-end 0) (match-beginning 0)) ?\ )))
- ;; Mark the hlines by setting the corresponding element to nil
- ;; At the same time, we remove trailing space.
- (setq lines (mapcar (lambda (l)
- (if (string-match "^ *|-" l)
- nil
- (if (string-match "[ \t]+$" l)
- (substring l 0 (match-beginning 0))
- l)))
- lines))
- ;; Get the data fields by splitting the lines.
- (setq fields (mapcar
- (lambda (l)
- (org-split-string l " *| *"))
- (delq nil (copy-sequence lines))))
- ;; How many fields in the longest line?
- (condition-case nil
- (setq maxfields (apply 'max (mapcar 'length fields)))
- (error
- (kill-region beg end)
- (org-table-create org-table-default-size)
- (user-error "Empty table - created default table")))
- ;; A list of empty strings to fill any short rows on output
- (setq emptystrings (make-list maxfields ""))
- ;; Check for special formatting.
- (setq i -1)
- (while (< (setq i (1+ i)) maxfields) ;; Loop over all columns
- (setq column (mapcar (lambda (x) (or (nth i x) "")) fields))
- ;; Check if there is an explicit width specified
- (setq fmax nil)
- (when (or narrow falign)
- (setq c column fmax nil falign1 nil)
- (while c
- (setq e (pop c))
- (when (and (stringp e) (string-match "^<\\([lrc]\\)?\\([0-9]+\\)?>$" e))
- (if (match-end 1) (setq falign1 (match-string 1 e)))
- (if (and org-table-do-narrow (match-end 2))
- (setq fmax (string-to-number (match-string 2 e)) c nil))))
- ;; Find fields that are wider than fmax, and shorten them
- (when fmax
- (loop for xx in column do
- (when (and (stringp xx)
- (> (org-string-width xx) fmax))
- (org-add-props xx nil
- 'help-echo
- (concat "Clipped table field, use C-c ` to edit. Full value is:\n" (org-no-properties (copy-sequence xx))))
- (setq f1 (min fmax (or (string-match org-bracket-link-regexp xx) fmax)))
- (unless (> f1 1)
- (user-error "Cannot narrow field starting with wide link \"%s\""
- (match-string 0 xx)))
- (add-text-properties f1 (length xx) (list 'org-cwidth t) xx)
- (add-text-properties (- f1 2) f1
- (list 'display org-narrow-column-arrow)
- xx)))))
- ;; Get the maximum width for each column
- (push (apply 'max (or fmax 1) 1 (mapcar 'org-string-width column))
- lengths)
- ;; Get the fraction of numbers, to decide about alignment of the column
- (if falign1
- (push (equal (downcase falign1) "r") typenums)
- (setq cnt 0 frac 0.0)
- (loop for x in column do
- (if (equal x "")
- nil
- (setq frac ( / (+ (* frac cnt)
- (if (string-match org-table-number-regexp x) 1 0))
- (setq cnt (1+ cnt))))))
- (push (>= frac org-table-number-fraction) typenums)))
- (setq lengths (nreverse lengths) typenums (nreverse typenums))
-
- ;; Store the alignment of this table, for later editing of single fields
- (setq org-table-last-alignment typenums
- org-table-last-column-widths lengths)
-
- ;; With invisible characters, `format' does not get the field width right
- ;; So we need to make these fields wide by hand.
- (when (or links emph raise)
- (loop for i from 0 upto (1- maxfields) do
- (setq len (nth i lengths))
- (loop for j from 0 upto (1- (length fields)) do
- (setq c (nthcdr i (car (nthcdr j fields))))
- (if (and (stringp (car c))
- (or (text-property-any 0 (length (car c))
- 'invisible 'org-link (car c))
- (text-property-any 0 (length (car c))
- 'org-dwidth t (car c)))
- (< (org-string-width (car c)) len))
- (progn
- (setq space (make-string (- len (org-string-width (car c))) ?\ ))
- (setcar c (if (nth i typenums)
- (concat space (car c))
- (concat (car c) space))))))))
-
- ;; Compute the formats needed for output of the table
- (setq rfmt (concat indent "|") hfmt (concat indent "|"))
- (while (setq l (pop lengths))
- (setq ty (if (pop typenums) "" "-")) ; number types flushright
- (setq rfmt (concat rfmt (format rfmt1 ty l))
- hfmt (concat hfmt (format hfmt1 (make-string l ?-)))))
- (setq rfmt (concat rfmt "\n")
- hfmt (concat (substring hfmt 0 -1) "|\n"))
-
- (setq new (mapconcat
- (lambda (l)
- (if l (apply 'format rfmt
- (append (pop fields) emptystrings))
- hfmt))
- lines ""))
- (move-marker org-table-aligned-begin-marker (point))
- (insert new)
- ;; Replace the old one
- (delete-region (point) end)
- (move-marker end nil)
- (move-marker org-table-aligned-end-marker (point))
- (when (and orgtbl-mode (not (derived-mode-p 'org-mode)))
- (goto-char org-table-aligned-begin-marker)
- (while (org-hide-wide-columns org-table-aligned-end-marker)))
- ;; Try to move to the old location
- (org-goto-line winstartline)
- (setq winstart (point-at-bol))
- (org-goto-line linepos)
- (when (eq (window-buffer (selected-window)) (current-buffer))
- (set-window-start (selected-window) winstart 'noforce))
- (org-table-goto-column colpos)
- (and org-table-overlay-coordinates (org-table-overlay-coordinates))
- (setq org-table-may-need-update nil)
- ))
+ (let* ((beg (org-table-begin))
+ (end (copy-marker (org-table-end))))
+ (org-table-save-field
+ ;; Make sure invisible characters in the table are at the right
+ ;; place since column widths take them into account.
+ (font-lock-fontify-region beg end)
+ (move-marker org-table-aligned-begin-marker beg)
+ (move-marker org-table-aligned-end-marker end)
+ (goto-char beg)
+ (let* ((indent (progn (looking-at "[ \t]*") (match-string 0)))
+ ;; Table's rows. Separators are replaced by nil. Trailing
+ ;; spaces are also removed.
+ (lines (mapcar (lambda (l)
+ (and (not (string-match-p "\\`[ \t]*|-" l))
+ (let ((l (org-trim l)))
+ (remove-text-properties
+ 0 (length l) '(display t org-cwidth t) l)
+ l)))
+ (org-split-string (buffer-substring beg end) "\n")))
+ ;; Get the data fields by splitting the lines.
+ (fields (mapcar (lambda (l) (org-split-string l " *| *"))
+ (remq nil lines)))
+ ;; Compute number of fields in the longest line. If the
+ ;; table contains no field, create a default table.
+ (maxfields (if fields (apply #'max (mapcar #'length fields))
+ (kill-region beg end)
+ (org-table-create org-table-default-size)
+ (user-error "Empty table - created default table")))
+ ;; A list of empty strings to fill any short rows on output.
+ (emptycells (make-list maxfields ""))
+ lengths typenums)
+ ;; Check for special formatting.
+ (dotimes (i maxfields)
+ (let ((column (mapcar (lambda (x) (or (nth i x) "")) fields))
+ fmax falign)
+ ;; Look for an explicit width or alignment.
+ (when (save-excursion
+ (or (re-search-forward "| *<[lrc][0-9]*> *\\(|\\|$\\)" end t)
+ (and org-table-do-narrow
+ (re-search-forward
+ "| *<[lrc]?[0-9]+> *\\(|\\|$\\)" end t))))
+ (catch :exit
+ (dolist (cell column)
+ (when (string-match "\\`<\\([lrc]\\)?\\([0-9]+\\)?>\\'" cell)
+ (when (match-end 1) (setq falign (match-string 1 cell)))
+ (when (and org-table-do-narrow (match-end 2))
+ (setq fmax (string-to-number (match-string 2 cell))))
+ (when (or falign fmax) (throw :exit nil)))))
+ ;; Find fields that are wider than FMAX, and shorten them.
+ (when fmax
+ (dolist (x column)
+ (when (> (org-string-width x) fmax)
+ (org-add-props x nil
+ 'help-echo
+ (concat
+ "Clipped table field, use `\\[org-table-edit-field]' to \
+edit. Full value is:\n"
+ (substring-no-properties x)))
+ (let ((l (length x))
+ (f1 (min fmax
+ (or (string-match org-bracket-link-regexp x)
+ fmax)))
+ (f2 1))
+ (unless (> f1 1)
+ (user-error
+ "Cannot narrow field starting with wide link \"%s\""
+ (match-string 0 x)))
+ (if (= (org-string-width x) l) (setq f2 f1)
+ (setq f2 1)
+ (while (< (org-string-width (substring x 0 f2)) f1)
+ (cl-incf f2)))
+ (add-text-properties f2 l (list 'org-cwidth t) x)
+ (add-text-properties
+ (if (>= (string-width (substring x (1- f2) f2)) 2) (1- f2)
+ (- f2 2))
+ f2
+ (list 'display org-narrow-column-arrow)
+ x))))))
+ ;; Get the maximum width for each column
+ (push (apply #'max (or fmax 1) 1 (mapcar #'org-string-width column))
+ lengths)
+ ;; Get the fraction of numbers among non-empty cells to
+ ;; decide about alignment of the column.
+ (if falign (push (equal (downcase falign) "r") typenums)
+ (let ((cnt 0)
+ (frac 0.0))
+ (dolist (x column)
+ (unless (equal x "")
+ (setq frac
+ (/ (+ (* frac cnt)
+ (if (string-match-p org-table-number-regexp x)
+ 1
+ 0))
+ (cl-incf cnt)))))
+ (push (>= frac org-table-number-fraction) typenums)))))
+ (setq lengths (nreverse lengths))
+ (setq typenums (nreverse typenums))
+ ;; Store alignment of this table, for later editing of single
+ ;; fields.
+ (setq org-table-last-alignment typenums)
+ (setq org-table-last-column-widths lengths)
+ ;; With invisible characters, `format' does not get the field
+ ;; width right So we need to make these fields wide by hand.
+ ;; Invisible characters may be introduced by fontified links,
+ ;; emphasis, macros or sub/superscripts.
+ (when (or (text-property-any beg end 'invisible 'org-link)
+ (text-property-any beg end 'invisible t))
+ (dotimes (i maxfields)
+ (let ((len (nth i lengths)))
+ (dotimes (j (length fields))
+ (let* ((c (nthcdr i (nth j fields)))
+ (cell (car c)))
+ (when (and
+ (stringp cell)
+ (let ((l (length cell)))
+ (or (text-property-any 0 l 'invisible 'org-link cell)
+ (text-property-any beg end 'invisible t)))
+ (< (org-string-width cell) len))
+ (let ((s (make-string (- len (org-string-width cell)) ?\s)))
+ (setcar c (if (nth i typenums) (concat s cell)
+ (concat cell s))))))))))
+
+ ;; Compute the formats needed for output of the table.
+ (let ((hfmt (concat indent "|"))
+ (rfmt (concat indent "|"))
+ (rfmt1 " %%%s%ds |")
+ (hfmt1 "-%s-+"))
+ (dolist (l lengths (setq hfmt (concat (substring hfmt 0 -1) "|")))
+ (let ((ty (if (pop typenums) "" "-"))) ; Flush numbers right.
+ (setq rfmt (concat rfmt (format rfmt1 ty l)))
+ (setq hfmt (concat hfmt (format hfmt1 (make-string l ?-))))))
+ ;; Replace modified lines only. Check not only contents, but
+ ;; also columns' width.
+ (dolist (l lines)
+ (let ((line
+ (if l (apply #'format rfmt (append (pop fields) emptycells))
+ hfmt))
+ (previous (buffer-substring (point) (line-end-position))))
+ (if (and (equal previous line)
+ (let ((a 0)
+ (b 0))
+ (while (and (progn
+ (setq a (next-single-property-change
+ a 'org-cwidth previous))
+ (setq b (next-single-property-change
+ b 'org-cwidth line)))
+ (eq a b)))
+ (eq a b)))
+ (forward-line)
+ (insert line "\n")
+ (delete-region (point) (line-beginning-position 2))))))
+ (when (and orgtbl-mode (not (derived-mode-p 'org-mode)))
+ (goto-char org-table-aligned-begin-marker)
+ (while (org-hide-wide-columns org-table-aligned-end-marker)))
+ (set-marker end nil)
+ (when org-table-overlay-coordinates (org-table-overlay-coordinates))
+ (setq org-table-may-need-update nil)))))
;;;###autoload
(defun org-table-begin (&optional table-type)
"Find the beginning of the table and return its position.
-With argument TABLE-TYPE, go to the beginning of a table.el-type table."
- (save-excursion
- (if (not (re-search-backward
- (if table-type org-table-any-border-regexp
- org-table-border-regexp)
- nil t))
- (progn (goto-char (point-min)) (point))
- (goto-char (match-beginning 0))
- (beginning-of-line 2)
- (point))))
+With a non-nil optional argument TABLE-TYPE, return the beginning
+of a table.el-type table. This function assumes point is on
+a table."
+ (cond (table-type
+ (org-element-property :post-affiliated (org-element-at-point)))
+ ((save-excursion
+ (and (re-search-backward org-table-border-regexp nil t)
+ (line-beginning-position 2))))
+ (t (point-min))))
;;;###autoload
(defun org-table-end (&optional table-type)
"Find the end of the table and return its position.
-With argument TABLE-TYPE, go to the end of a table.el-type table."
+With a non-nil optional argument TABLE-TYPE, return the end of
+a table.el-type table. This function assumes point is on
+a table."
(save-excursion
- (if (not (re-search-forward
- (if table-type org-table-any-border-regexp
- org-table-border-regexp)
- nil t))
- (goto-char (point-max))
- (goto-char (match-beginning 0)))
- (point-marker)))
+ (cond (table-type
+ (goto-char (org-element-property :end (org-element-at-point)))
+ (skip-chars-backward " \t\n")
+ (line-beginning-position 2))
+ ((re-search-forward org-table-border-regexp nil t)
+ (match-beginning 0))
+ ;; When the line right after the table is the last line in
+ ;; the buffer with trailing spaces but no final newline
+ ;; character, be sure to catch the correct ending at its
+ ;; beginning. In any other case, ending is expected to be
+ ;; at point max.
+ (t (goto-char (point-max))
+ (skip-chars-backward " \t")
+ (if (bolp) (point) (line-end-position))))))
;;;###autoload
(defun org-table-justify-field-maybe (&optional new)
@@ -950,38 +946,40 @@ Optional argument NEW may specify text to replace the current field content."
((and (not new) org-table-may-need-update)) ; Realignment will happen anyway
((org-at-table-hline-p))
((and (not new)
- (or (not (equal (marker-buffer org-table-aligned-begin-marker)
- (current-buffer)))
+ (or (not (eq (marker-buffer org-table-aligned-begin-marker)
+ (current-buffer)))
(< (point) org-table-aligned-begin-marker)
(>= (point) org-table-aligned-end-marker)))
- ;; This is not the same table, force a full re-align
+ ;; This is not the same table, force a full re-align.
(setq org-table-may-need-update t))
- (t ;; realign the current field, based on previous full realign
- (let* ((pos (point)) s
- (col (org-table-current-column))
- (num (if (> col 0) (nth (1- col) org-table-last-alignment)))
- l f n o e)
+ (t
+ ;; Realign the current field, based on previous full realign.
+ (let ((pos (point))
+ (col (org-table-current-column)))
(when (> col 0)
- (skip-chars-backward "^|\n")
- (if (looking-at " *\\([^|\n]*?\\) *\\(|\\|$\\)")
- (progn
- (setq s (match-string 1)
- o (match-string 0)
- l (max 1 (- (match-end 0) (match-beginning 0) 3))
- e (not (= (match-beginning 2) (match-end 2))))
- (setq f (format (if num " %%%ds %s" " %%-%ds %s")
- l (if e "|" (setq org-table-may-need-update t) ""))
- n (format f s))
- (if new
- (if (<= (length new) l) ;; FIXME: length -> str-width?
- (setq n (format f new))
- (setq n (concat new "|") org-table-may-need-update t)))
- (if (equal (string-to-char n) ?-) (setq n (concat " " n)))
- (or (equal n o)
- (let (org-table-may-need-update)
- (replace-match n t t))))
- (setq org-table-may-need-update t))
- (goto-char pos))))))
+ (skip-chars-backward "^|")
+ (if (not (looking-at " *\\([^|\n]*?\\) *\\(|\\|$\\)"))
+ (setq org-table-may-need-update t)
+ (let* ((numbers? (nth (1- col) org-table-last-alignment))
+ (cell (match-string 0))
+ (field (match-string 1))
+ (len (max 1 (- (org-string-width cell) 3)))
+ (properly-closed? (/= (match-beginning 2) (match-end 2)))
+ (fmt (format (if numbers? " %%%ds %s" " %%-%ds %s")
+ len
+ (if properly-closed? "|"
+ (setq org-table-may-need-update t)
+ "")))
+ (new-cell
+ (cond ((not new) (format fmt field))
+ ((<= (org-string-width new) len) (format fmt new))
+ (t
+ (setq org-table-may-need-update t)
+ (format " %s |" new)))))
+ (unless (equal new-cell cell)
+ (let (org-table-may-need-update)
+ (replace-match new-cell t t)))
+ (goto-char pos))))))))
;;;###autoload
(defun org-table-next-field ()
@@ -1036,9 +1034,10 @@ Before doing so, re-align the table if necessary."
(goto-char (match-end 0))))
(defun org-table-beginning-of-field (&optional n)
- "Move to the end of the current table field.
-If already at or after the end, move to the end of the next table field.
-With numeric argument N, move N-1 fields forward first."
+ "Move to the beginning of the current table field.
+If already at or before the beginning, move to the beginning of the
+previous field.
+With numeric argument N, move N-1 fields backward first."
(interactive "p")
(let ((pos (point)))
(while (> n 1)
@@ -1051,10 +1050,9 @@ With numeric argument N, move N-1 fields forward first."
(if (>= (point) pos) (org-table-beginning-of-field 2))))
(defun org-table-end-of-field (&optional n)
- "Move to the beginning of the current table field.
-If already at or before the beginning, move to the beginning of the
-previous field.
-With numeric argument N, move N-1 fields backward first."
+ "Move to the end of the current table field.
+If already at or after the end, move to the end of the next table field.
+With numeric argument N, move N-1 fields forward first."
(interactive "p")
(let ((pos (point)))
(while (> n 1)
@@ -1093,30 +1091,36 @@ Before doing so, re-align the table if necessary."
;;;###autoload
(defun org-table-copy-down (n)
- "Copy a field down in the current column.
-If the field at the cursor is empty, copy into it the content of
-the nearest non-empty field above. With argument N, use the Nth
-non-empty field. If the current field is not empty, it is copied
-down to the next row, and the cursor is moved with it.
-Therefore, repeating this command causes the column to be filled
-row-by-row.
+ "Copy the value of the current field one row below.
+
+If the field at the cursor is empty, copy the content of the
+nearest non-empty field above. With argument N, use the Nth
+non-empty field.
+
+If the current field is not empty, it is copied down to the next
+row, and the cursor is moved with it. Therefore, repeating this
+command causes the column to be filled row-by-row.
+
If the variable `org-table-copy-increment' is non-nil and the
field is an integer or a timestamp, it will be incremented while
-copying. In the case of a timestamp, increment by one day."
+copying. By default, increment by the difference between the
+value in the current field and the one in the field above. To
+increment using a fixed integer, set `org-table-copy-increment'
+to a number. In the case of a timestamp, increment by days."
(interactive "p")
(let* ((colpos (org-table-current-column))
(col (current-column))
(field (save-excursion (org-table-get-field)))
+ (field-up (or (save-excursion
+ (org-table-get (1- (org-table-current-line))
+ (org-table-current-column))) ""))
(non-empty (string-match "[^ \t]" field))
+ (non-empty-up (string-match "[^ \t]" field-up))
(beg (org-table-begin))
(orig-n n)
- txt)
+ txt txt-up inc)
(org-table-check-inside-data-field)
- (if non-empty
- (progn
- (setq txt (org-trim field))
- (org-table-next-row)
- (org-table-blank-field))
+ (if (not non-empty)
(save-excursion
(setq txt
(catch 'exit
@@ -1127,35 +1131,60 @@ copying. In the case of a timestamp, increment by one day."
(if (and (looking-at
"|[ \t]*\\([^| \t][^|]*?\\)[ \t]*|")
(<= (setq n (1- n)) 0))
- (throw 'exit (match-string 1))))))))
- (if txt
- (progn
- (if (and org-table-copy-increment
- (not (equal orig-n 0))
- (string-match "^[0-9]+$" txt)
- (< (string-to-number txt) 100000000))
- (setq txt (format "%d" (+ (string-to-number txt) 1))))
- (insert txt)
- (org-move-to-column col)
- (if (and org-table-copy-increment (org-at-timestamp-p t))
- (org-timestamp-up-day)
- (org-table-maybe-recalculate-line))
- (org-table-align)
- (org-move-to-column col))
- (user-error "No non-empty field found"))))
+ (throw 'exit (match-string 1))))))
+ (setq field-up
+ (catch 'exit
+ (while (progn (beginning-of-line 1)
+ (re-search-backward org-table-dataline-regexp
+ beg t))
+ (org-table-goto-column colpos t)
+ (if (and (looking-at
+ "|[ \t]*\\([^| \t][^|]*?\\)[ \t]*|")
+ (<= (setq n (1- n)) 0))
+ (throw 'exit (match-string 1))))))
+ (setq non-empty-up (and field-up (string-match "[^ \t]" field-up))))
+ ;; Above field was not empty, go down to the next row
+ (setq txt (org-trim field))
+ (org-table-next-row)
+ (org-table-blank-field))
+ (if non-empty-up (setq txt-up (org-trim field-up)))
+ (setq inc (cond
+ ((numberp org-table-copy-increment) org-table-copy-increment)
+ (txt-up (cond ((and (string-match org-ts-regexp3 txt-up)
+ (string-match org-ts-regexp3 txt))
+ (- (org-time-string-to-absolute txt)
+ (org-time-string-to-absolute txt-up)))
+ ((string-match org-ts-regexp3 txt) 1)
+ ((string-match "\\([-+]\\)?[0-9]+\\(?:\.[0-9]+\\)?" txt-up)
+ (- (string-to-number txt)
+ (string-to-number (match-string 0 txt-up))))
+ (t 1)))
+ (t 1)))
+ (if (not txt)
+ (user-error "No non-empty field found")
+ (if (and org-table-copy-increment
+ (not (equal orig-n 0))
+ (string-match-p "^[-+^/*0-9eE.]+$" txt)
+ (< (string-to-number txt) 100000000))
+ (setq txt (calc-eval (concat txt "+" (number-to-string inc)))))
+ (insert txt)
+ (org-move-to-column col)
+ (if (and org-table-copy-increment (org-at-timestamp-p t))
+ (org-timestamp-up-day inc)
+ (org-table-maybe-recalculate-line))
+ (org-table-align)
+ (org-move-to-column col))))
(defun org-table-check-inside-data-field (&optional noerror)
"Is point inside a table data field?
I.e. not on a hline or before the first or after the last column?
This actually throws an error, so it aborts the current command."
- (if (or (not (org-at-table-p))
- (= (org-table-current-column) 0)
- (org-at-table-hline-p)
- (looking-at "[ \t]*$"))
- (if noerror
- nil
- (user-error "Not in table data field"))
- t))
+ (cond ((and (org-at-table-p)
+ (not (save-excursion (skip-chars-backward " \t") (bolp)))
+ (not (org-at-table-hline-p))
+ (not (looking-at "[ \t]*$"))))
+ (noerror nil)
+ (t (user-error "Not in table data field"))))
(defvar org-table-clip nil
"Clipboard for table regions.")
@@ -1166,7 +1195,7 @@ If LINE is larger than the number of data lines in the table, the function
returns nil. However, if COLUMN is too large, we will simply return an
empty string.
If LINE is nil, use the current line.
-If column is nil, use the current column."
+If COLUMN is nil, use the current column."
(setq column (or column (org-table-current-column)))
(save-excursion
(and (or (not line) (org-table-goto-line line))
@@ -1206,7 +1235,7 @@ Return t when the line exists, nil if it does not exist."
"Blank the current table field or active region."
(interactive)
(org-table-check-inside-data-field)
- (if (and (org-called-interactively-p 'any) (org-region-active-p))
+ (if (and (called-interactively-p 'any) (org-region-active-p))
(let (org-table-clip)
(org-table-cut-region (region-beginning) (region-end)))
(skip-chars-backward "^|")
@@ -1221,52 +1250,53 @@ Return t when the line exists, nil if it does not exist."
(defun org-table-get-field (&optional n replace)
"Return the value of the field in column N of current row.
-N defaults to current field.
-If REPLACE is a string, replace field with this value. The return value
-is always the old value."
- (and n (org-table-goto-column n))
+N defaults to current column. If REPLACE is a string, replace
+field with this value. The return value is always the old
+value."
+ (when n (org-table-goto-column n))
(skip-chars-backward "^|\n")
- (backward-char 1)
- (if (looking-at "|[^|\r\n]*")
- (let* ((pos (match-beginning 0))
- (val (buffer-substring (1+ pos) (match-end 0))))
- (if replace
- (replace-match (concat "|" (if (equal replace "") " " replace))
- t t))
- (goto-char (min (point-at-eol) (+ 2 pos)))
- val)
- (forward-char 1) ""))
+ (if (or (bolp) (looking-at-p "[ \t]*$"))
+ ;; Before first column or after last one.
+ ""
+ (looking-at "[^|\r\n]*")
+ (let* ((pos (match-beginning 0))
+ (val (buffer-substring pos (match-end 0))))
+ (when replace
+ (replace-match (if (equal replace "") " " replace) t t))
+ (goto-char (min (line-end-position) (1+ pos)))
+ val)))
;;;###autoload
-(defun org-table-field-info (arg)
+(defun org-table-field-info (_arg)
"Show info about the current field, and highlight any reference at point."
(interactive "P")
(unless (org-at-table-p) (user-error "Not at a table"))
- (org-table-get-specials)
+ (org-table-analyze)
(save-excursion
(let* ((pos (point))
(col (org-table-current-column))
(cname (car (rassoc (int-to-string col) org-table-column-names)))
- (name (car (rassoc (list (org-current-line) col)
+ (name (car (rassoc (list (count-lines org-table-current-begin-pos
+ (line-beginning-position))
+ col)
org-table-named-field-locations)))
(eql (org-table-expand-lhs-ranges
(mapcar
(lambda (e)
- (cons (org-table-formula-handle-first/last-rc
- (car e)) (cdr e)))
+ (cons (org-table-formula-handle-first/last-rc (car e))
+ (cdr e)))
(org-table-get-stored-formulas))))
(dline (org-table-current-dline))
(ref (format "@%d$%d" dline col))
(ref1 (org-table-convert-refs-to-an ref))
+ ;; Prioritize field formulas over column formulas.
(fequation (or (assoc name eql) (assoc ref eql)))
- (cequation (assoc (int-to-string col) eql))
+ (cequation (assoc (format "$%d" col) eql))
(eqn (or fequation cequation)))
- (if (and eqn (get-text-property 0 :orig-eqn (car eqn)))
- (setq eqn (get-text-property 0 :orig-eqn (car eqn))))
+ (let ((p (and eqn (get-text-property 0 :orig-eqn (car eqn)))))
+ (when p (setq eqn p)))
(goto-char pos)
- (condition-case nil
- (org-table-show-reference 'local)
- (error nil))
+ (ignore-errors (org-table-show-reference 'local))
(message "line @%d, col $%s%s, ref @%d$%d or %s%s%s"
dline col
(if cname (concat " or $" cname) "")
@@ -1277,39 +1307,42 @@ is always the old value."
(concat ", formula: "
(org-table-formula-to-user
(concat
- (if (string-match "^[$@]"(car eqn)) "" "$")
+ (if (or (string-prefix-p "$" (car eqn))
+ (string-prefix-p "@" (car eqn)))
+ ""
+ "$")
(car eqn) "=" (cdr eqn))))
"")))))
(defun org-table-current-column ()
"Find out which column we are in."
(interactive)
- (if (org-called-interactively-p 'any) (org-table-check-inside-data-field))
+ (when (called-interactively-p 'any) (org-table-check-inside-data-field))
(save-excursion
- (let ((cnt 0) (pos (point)))
- (beginning-of-line 1)
- (while (search-forward "|" pos t)
- (setq cnt (1+ cnt)))
- (when (org-called-interactively-p 'interactive)
- (message "In table column %d" cnt))
- cnt)))
+ (let ((column 0) (pos (point)))
+ (beginning-of-line)
+ (while (search-forward "|" pos t) (cl-incf column))
+ (when (called-interactively-p 'interactive)
+ (message "In table column %d" column))
+ column)))
;;;###autoload
(defun org-table-current-dline ()
"Find out what table data line we are in.
Only data lines count for this."
(interactive)
- (when (org-called-interactively-p 'any)
+ (when (called-interactively-p 'any)
(org-table-check-inside-data-field))
(save-excursion
- (let ((cnt 0) (pos (point)))
+ (let ((c 0)
+ (pos (point)))
(goto-char (org-table-begin))
(while (<= (point) pos)
- (if (looking-at org-table-dataline-regexp) (setq cnt (1+ cnt)))
- (beginning-of-line 2))
- (when (org-called-interactively-p 'any)
- (message "This is table line %d" cnt))
- cnt)))
+ (when (looking-at org-table-dataline-regexp) (cl-incf c))
+ (forward-line))
+ (when (called-interactively-p 'any)
+ (message "This is table line %d" c))
+ c)))
;;;###autoload
(defun org-table-goto-column (n &optional on-delim force)
@@ -1338,25 +1371,19 @@ However, when FORCE is non-nil, create new columns if necessary."
(defun org-table-insert-column ()
"Insert a new column into the table."
(interactive)
- (if (not (org-at-table-p))
- (user-error "Not at a table"))
+ (unless (org-at-table-p) (user-error "Not at a table"))
(org-table-find-dataline)
(let* ((col (max 1 (org-table-current-column)))
(beg (org-table-begin))
- (end (org-table-end))
- ;; Current cursor position
- (linepos (org-current-line))
- (colpos col))
- (goto-char beg)
- (while (< (point) end)
- (if (org-at-table-hline-p)
- nil
- (org-table-goto-column col t)
- (insert "| "))
- (beginning-of-line 2))
- (move-marker end nil)
- (org-goto-line linepos)
- (org-table-goto-column colpos)
+ (end (copy-marker (org-table-end))))
+ (org-table-save-field
+ (goto-char beg)
+ (while (< (point) end)
+ (unless (org-at-table-hline-p)
+ (org-table-goto-column col t)
+ (insert "| "))
+ (forward-line)))
+ (set-marker end nil)
(org-table-align)
(when (or (not org-table-fix-formulas-confirm)
(funcall org-table-fix-formulas-confirm "Fix formulas? "))
@@ -1384,58 +1411,55 @@ However, when FORCE is non-nil, create new columns if necessary."
(defun org-table-line-to-dline (line &optional above)
"Turn a buffer line number into a data line number.
+
If there is no data line in this line, return nil.
-If there is no matching dline (most likely te reference was a hline), the
-first dline below it is used. When ABOVE is non-nil, the one above is used."
- (catch 'exit
- (let ((ll (length org-table-dlines))
- i)
- (if above
- (progn
- (setq i (1- ll))
- (while (> i 0)
- (if (<= (aref org-table-dlines i) line)
- (throw 'exit i))
- (setq i (1- i))))
- (setq i 1)
- (while (< i ll)
- (if (>= (aref org-table-dlines i) line)
- (throw 'exit i))
- (setq i (1+ i)))))
- nil))
+
+If there is no matching dline (most likely the reference was
+a hline), the first dline below it is used. When ABOVE is
+non-nil, the one above is used."
+ (let ((min 1)
+ (max (1- (length org-table-dlines))))
+ (cond ((or (> (aref org-table-dlines min) line)
+ (< (aref org-table-dlines max) line))
+ nil)
+ ((= (aref org-table-dlines max) line) max)
+ (t (catch 'exit
+ (while (> (- max min) 1)
+ (let* ((mean (/ (+ max min) 2))
+ (v (aref org-table-dlines mean)))
+ (cond ((= v line) (throw 'exit mean))
+ ((> v line) (setq max mean))
+ (t (setq min mean)))))
+ (if above min max))))))
;;;###autoload
(defun org-table-delete-column ()
"Delete a column from the table."
(interactive)
- (if (not (org-at-table-p))
- (user-error "Not at a table"))
+ (unless (org-at-table-p) (user-error "Not at a table"))
(org-table-find-dataline)
(org-table-check-inside-data-field)
- (let* ((col (org-table-current-column))
- (beg (org-table-begin))
- (end (org-table-end))
- ;; Current cursor position
- (linepos (org-current-line))
- (colpos col))
- (goto-char beg)
- (while (< (point) end)
- (if (org-at-table-hline-p)
- nil
- (org-table-goto-column col t)
- (and (looking-at "|[^|\n]+|")
- (replace-match "|")))
- (beginning-of-line 2))
- (move-marker end nil)
- (org-goto-line linepos)
- (org-table-goto-column colpos)
+ (let ((col (org-table-current-column))
+ (beg (org-table-begin))
+ (end (copy-marker (org-table-end))))
+ (org-table-save-field
+ (goto-char beg)
+ (while (< (point) end)
+ (if (org-at-table-hline-p)
+ nil
+ (org-table-goto-column col t)
+ (and (looking-at "|[^|\n]+|")
+ (replace-match "|")))
+ (forward-line)))
+ (set-marker end nil)
+ (org-table-goto-column (max 1 (1- col)))
(org-table-align)
(when (or (not org-table-fix-formulas-confirm)
(funcall org-table-fix-formulas-confirm "Fix formulas? "))
- (org-table-fix-formulas "$" (list (cons (number-to-string col) "INVALID"))
- col -1 col)
- (org-table-fix-formulas "$LR" (list (cons (number-to-string col) "INVALID"))
- col -1 col))))
+ (org-table-fix-formulas
+ "$" (list (cons (number-to-string col) "INVALID")) col -1 col)
+ (org-table-fix-formulas
+ "$LR" (list (cons (number-to-string col) "INVALID")) col -1 col))))
;;;###autoload
(defun org-table-move-column-right ()
@@ -1452,31 +1476,29 @@ first dline below it is used. When ABOVE is non-nil, the one above is used."
(defun org-table-move-column (&optional left)
"Move the current column to the right. With arg LEFT, move to the left."
(interactive "P")
- (if (not (org-at-table-p))
- (user-error "Not at a table"))
+ (unless (org-at-table-p) (user-error "Not at a table"))
(org-table-find-dataline)
(org-table-check-inside-data-field)
(let* ((col (org-table-current-column))
(col1 (if left (1- col) col))
+ (colpos (if left (1- col) (1+ col)))
(beg (org-table-begin))
- (end (org-table-end))
- ;; Current cursor position
- (linepos (org-current-line))
- (colpos (if left (1- col) (1+ col))))
- (if (and left (= col 1))
- (user-error "Cannot move column further left"))
- (if (and (not left) (looking-at "[^|\n]*|[^|\n]*$"))
- (user-error "Cannot move column further right"))
- (goto-char beg)
- (while (< (point) end)
- (if (org-at-table-hline-p)
- nil
- (org-table-goto-column col1 t)
- (and (looking-at "|\\([^|\n]+\\)|\\([^|\n]+\\)|")
- (replace-match "|\\2|\\1|")))
- (beginning-of-line 2))
- (move-marker end nil)
- (org-goto-line linepos)
+ (end (copy-marker (org-table-end))))
+ (when (and left (= col 1))
+ (user-error "Cannot move column further left"))
+ (when (and (not left) (looking-at "[^|\n]*|[^|\n]*$"))
+ (user-error "Cannot move column further right"))
+ (org-table-save-field
+ (goto-char beg)
+ (while (< (point) end)
+ (unless (org-at-table-hline-p)
+ (org-table-goto-column col1 t)
+ (when (looking-at "|\\([^|\n]+\\)|\\([^|\n]+\\)|")
+ (transpose-regions
+ (match-beginning 1) (match-end 1)
+ (match-beginning 2) (match-end 2))))
+ (forward-line)))
+ (set-marker end nil)
(org-table-goto-column colpos)
(org-table-align)
(when (or (not org-table-fix-formulas-confirm)
@@ -1538,19 +1560,21 @@ first dline below it is used. When ABOVE is non-nil, the one above is used."
"Insert a new row above the current line into the table.
With prefix ARG, insert below the current line."
(interactive "P")
- (if (not (org-at-table-p))
- (user-error "Not at a table"))
- (let* ((line (buffer-substring (point-at-bol) (point-at-eol)))
+ (unless (org-at-table-p) (user-error "Not at a table"))
+ (let* ((line (buffer-substring (line-beginning-position) (line-end-position)))
(new (org-table-clean-line line)))
;; Fix the first field if necessary
(if (string-match "^[ \t]*| *[#$] *|" line)
(setq new (replace-match (match-string 0 line) t t new)))
(beginning-of-line (if arg 2 1))
+ ;; Buffer may not end of a newline character, so ensure
+ ;; (beginning-of-line 2) moves point to a new line.
+ (unless (bolp) (insert "\n"))
(let (org-table-may-need-update) (insert-before-markers new "\n"))
(beginning-of-line 0)
- (re-search-forward "| ?" (point-at-eol) t)
- (and (or org-table-may-need-update org-table-overlay-coordinates)
- (org-table-align))
+ (re-search-forward "| ?" (line-end-position) t)
+ (when (or org-table-may-need-update org-table-overlay-coordinates)
+ (org-table-align))
(when (or (not org-table-fix-formulas-confirm)
(funcall org-table-fix-formulas-confirm "Fix formulas? "))
(org-table-fix-formulas "@" nil (1- (org-table-current-dline)) 1))))
@@ -1563,7 +1587,7 @@ With prefix ABOVE, insert above the current line."
(if (not (org-at-table-p))
(user-error "Not at a table"))
(when (eobp) (insert "\n") (backward-char 1))
- (if (not (string-match "|[ \t]*$" (org-current-line-string)))
+ (if (not (string-match-p "|[ \t]*$" (org-current-line-string)))
(org-table-align))
(let ((line (org-table-clean-line
(buffer-substring (point-at-bol) (point-at-eol))))
@@ -1623,7 +1647,8 @@ In particular, this does handle wide and invisible characters."
dline -1 dline))))
;;;###autoload
-(defun org-table-sort-lines (with-case &optional sorting-type)
+(defun org-table-sort-lines
+ (&optional with-case sorting-type getkey-func compare-func interactive?)
"Sort table lines according to the column at point.
The position of point indicates the column to be used for
@@ -1636,76 +1661,112 @@ should be in the last line to be included into the sorting.
The command then prompts for the sorting type which can be
alphabetically, numerically, or by time (as given in a time stamp
-in the field). Sorting in reverse order is also possible.
+in the field, or as a HH:MM value). Sorting in reverse order is
+also possible.
With prefix argument WITH-CASE, alphabetic sorting will be case-sensitive.
If SORTING-TYPE is specified when this function is called from a Lisp
program, no prompting will take place. SORTING-TYPE must be a character,
-any of (?a ?A ?n ?N ?t ?T) where the capital letter indicate that sorting
-should be done in reverse order."
- (interactive "P")
- (let* ((thisline (org-current-line))
- (thiscol (org-table-current-column))
- (otc org-table-overlay-coordinates)
- beg end bcol ecol tend tbeg column lns pos)
- (when (equal thiscol 0)
- (if (org-called-interactively-p 'any)
- (setq thiscol
- (string-to-number
- (read-string "Use column N for sorting: ")))
- (setq thiscol 1))
- (org-table-goto-column thiscol))
- (org-table-check-inside-data-field)
- (if (org-region-active-p)
- (progn
- (setq beg (region-beginning) end (region-end))
- (goto-char beg)
- (setq column (org-table-current-column)
- beg (point-at-bol))
- (goto-char end)
- (setq end (point-at-bol 2)))
- (setq column (org-table-current-column)
- pos (point)
- tbeg (org-table-begin)
- tend (org-table-end))
- (if (re-search-backward org-table-hline-regexp tbeg t)
- (setq beg (point-at-bol 2))
- (goto-char tbeg)
- (setq beg (point-at-bol 1)))
- (goto-char pos)
- (if (re-search-forward org-table-hline-regexp tend t)
- (setq end (point-at-bol 1))
- (goto-char tend)
- (setq end (point-at-bol))))
- (setq beg (move-marker (make-marker) beg)
- end (move-marker (make-marker) end))
- (untabify beg end)
- (goto-char beg)
- (org-table-goto-column column)
- (skip-chars-backward "^|")
- (setq bcol (current-column))
- (org-table-goto-column (1+ column))
- (skip-chars-backward "^|")
- (setq ecol (1- (current-column)))
- (org-table-goto-column column)
- (setq lns (mapcar (lambda(x) (cons
- (org-sort-remove-invisible
- (nth (1- column)
- (org-split-string x "[ \t]*|[ \t]*")))
- x))
- (org-split-string (buffer-substring beg end) "\n")))
- (setq lns (org-do-sort lns "Table" with-case sorting-type))
- (when org-table-overlay-coordinates
- (org-table-toggle-coordinate-overlays))
- (delete-region beg end)
- (move-marker beg nil)
- (move-marker end nil)
- (insert (mapconcat 'cdr lns "\n") "\n")
- (org-goto-line thisline)
- (org-table-goto-column thiscol)
- (when otc (org-table-toggle-coordinate-overlays))
- (message "%d lines sorted, based on column %d" (length lns) column)))
+any of (?a ?A ?n ?N ?t ?T ?f ?F) where the capital letters indicate that
+sorting should be done in reverse order.
+
+If the SORTING-TYPE is ?f or ?F, then GETKEY-FUNC specifies
+a function to be called to extract the key. It must return a value
+that is compatible with COMPARE-FUNC, the function used to compare
+entries.
+
+A non-nil value for INTERACTIVE? is used to signal that this
+function is being called interactively."
+ (interactive (list current-prefix-arg nil nil nil t))
+ (when (org-region-active-p) (goto-char (region-beginning)))
+ ;; Point must be either within a field or before a data line.
+ (save-excursion
+ (skip-chars-backward " \t")
+ (when (bolp) (search-forward "|" (line-end-position) t))
+ (org-table-check-inside-data-field))
+ ;; Set appropriate case sensitivity and column used for sorting.
+ (let ((column (let ((c (org-table-current-column)))
+ (cond ((> c 0) c)
+ (interactive?
+ (read-number "Use column N for sorting: "))
+ (t 1))))
+ (sorting-type
+ (or sorting-type
+ (read-char-exclusive "Sort Table: [a]lphabetic, [n]umeric, \
+\[t]ime, [f]unc. A/N/T/F means reversed: "))))
+ (save-restriction
+ ;; Narrow buffer to appropriate sorting area.
+ (if (org-region-active-p)
+ (progn (goto-char (region-beginning))
+ (narrow-to-region
+ (point)
+ (save-excursion (goto-char (region-end))
+ (line-beginning-position 2))))
+ (let ((start (org-table-begin))
+ (end (org-table-end)))
+ (narrow-to-region
+ (save-excursion
+ (if (re-search-backward org-table-hline-regexp start t)
+ (line-beginning-position 2)
+ start))
+ (if (save-excursion (re-search-forward org-table-hline-regexp end t))
+ (match-beginning 0)
+ end))))
+ ;; Determine arguments for `sort-subr'. Also record original
+ ;; position. `org-table-save-field' cannot help here since
+ ;; sorting is too much destructive.
+ (let* ((sort-fold-case (not with-case))
+ (coordinates
+ (cons (count-lines (point-min) (line-beginning-position))
+ (current-column)))
+ (extract-key-from-field
+ ;; Function to be called on the contents of the field
+ ;; used for sorting in the current row.
+ (cl-case sorting-type
+ ((?n ?N) #'string-to-number)
+ ((?a ?A) #'org-sort-remove-invisible)
+ ((?t ?T)
+ (lambda (f)
+ (cond ((string-match org-ts-regexp-both f)
+ (float-time
+ (org-time-string-to-time (match-string 0 f))))
+ ((string-match "[0-9]\\{1,2\\}:[0-9]\\{2\\}" f)
+ (org-hh:mm-string-to-minutes f))
+ (t 0))))
+ ((?f ?F)
+ (or getkey-func
+ (and interactive?
+ (org-read-function "Function for extracting keys: "))
+ (error "Missing key extractor to sort rows")))
+ (t (user-error "Invalid sorting type `%c'" sorting-type))))
+ (predicate
+ (cl-case sorting-type
+ ((?n ?N ?t ?T) #'<)
+ ((?a ?A) #'string<)
+ ((?f ?F)
+ (or compare-func
+ (and interactive?
+ (org-read-function
+ (concat "Function for comparing keys "
+ "(empty for default `sort-subr' predicate): ")
+ 'allow-empty)))))))
+ (goto-char (point-min))
+ (sort-subr (memq sorting-type '(?A ?N ?T ?F))
+ (lambda ()
+ (forward-line)
+ (while (and (not (eobp))
+ (not (looking-at org-table-dataline-regexp)))
+ (forward-line)))
+ #'end-of-line
+ (lambda ()
+ (funcall extract-key-from-field
+ (org-trim (org-table-get-field column))))
+ nil
+ predicate)
+ ;; Move back to initial field.
+ (forward-line (car coordinates))
+ (move-to-column (cdr coordinates))))))
;;;###autoload
(defun org-table-cut-region (beg end)
@@ -1725,34 +1786,31 @@ with `org-table-paste-rectangle'."
(if (org-region-active-p) (region-beginning) (point))
(if (org-region-active-p) (region-end) (point))
current-prefix-arg))
- (let* (l01 c01 l02 c02 l1 c1 l2 c2 ic1 ic2
- region cols
- (rpl (if cut " " nil)))
- (goto-char beg)
- (org-table-check-inside-data-field)
- (setq l01 (org-current-line)
- c01 (org-table-current-column))
- (goto-char end)
+ (goto-char (min beg end))
+ (org-table-check-inside-data-field)
+ (let ((beg (line-beginning-position))
+ (c01 (org-table-current-column))
+ region)
+ (goto-char (max beg end))
(org-table-check-inside-data-field)
- (setq l02 (org-current-line)
- c02 (org-table-current-column))
- (setq l1 (min l01 l02) l2 (max l01 l02)
- c1 (min c01 c02) c2 (max c01 c02))
- (catch 'exit
- (while t
- (catch 'nextline
- (if (> l1 l2) (throw 'exit t))
- (org-goto-line l1)
- (if (org-at-table-hline-p) (throw 'nextline (setq l1 (1+ l1))))
- (setq cols nil ic1 c1 ic2 c2)
- (while (< ic1 (1+ ic2))
- (push (org-table-get-field ic1 rpl) cols)
- (setq ic1 (1+ ic1)))
- (push (nreverse cols) region)
- (setq l1 (1+ l1)))))
- (setq org-table-clip (nreverse region))
- (if cut (org-table-align))
- org-table-clip))
+ (let* ((end (copy-marker (line-end-position)))
+ (c02 (org-table-current-column))
+ (column-start (min c01 c02))
+ (column-end (max c01 c02))
+ (column-number (1+ (- column-end column-start)))
+ (rpl (and cut " ")))
+ (goto-char beg)
+ (while (< (point) end)
+ (unless (org-at-table-hline-p)
+ ;; Collect every cell between COLUMN-START and COLUMN-END.
+ (let (cols)
+ (dotimes (c column-number)
+ (push (org-table-get-field (+ c column-start) rpl) cols))
+ (push (nreverse cols) region)))
+ (forward-line))
+ (set-marker end nil))
+ (when cut (org-table-align))
+ (setq org-table-clip (nreverse region))))
;;;###autoload
(defun org-table-paste-rectangle ()
@@ -1762,45 +1820,43 @@ will be overwritten. If the rectangle does not fit into the present table,
the table is enlarged as needed. The process ignores horizontal separator
lines."
(interactive)
- (unless (and org-table-clip (listp org-table-clip))
+ (unless (consp org-table-clip)
(user-error "First cut/copy a region to paste!"))
(org-table-check-inside-data-field)
- (let* ((clip org-table-clip)
- (line (org-current-line))
- (col (org-table-current-column))
+ (let* ((column (org-table-current-column))
(org-enable-table-editor t)
- (org-table-automatic-realign nil)
- c cols field)
- (while (setq cols (pop clip))
- (while (org-at-table-hline-p) (beginning-of-line 2))
- (if (not (org-at-table-p))
- (progn (end-of-line 0) (org-table-next-field)))
- (setq c col)
- (while (setq field (pop cols))
- (org-table-goto-column c nil 'force)
- (org-table-get-field nil field)
- (setq c (1+ c)))
- (beginning-of-line 2))
- (org-goto-line line)
- (org-table-goto-column col)
+ (org-table-automatic-realign nil))
+ (org-table-save-field
+ (dolist (row org-table-clip)
+ (while (org-at-table-hline-p) (forward-line))
+ ;; If we left the table, create a new row.
+ (when (and (bolp) (not (looking-at "[ \t]*|")))
+ (end-of-line 0)
+ (org-table-next-field))
+ (let ((c column))
+ (dolist (field row)
+ (org-table-goto-column c nil 'force)
+ (org-table-get-field nil field)
+ (cl-incf c)))
+ (forward-line)))
(org-table-align)))
;;;###autoload
(defun org-table-convert ()
"Convert from `org-mode' table to table.el and back.
-Obviously, this only works within limits. When an Org-mode table is
-converted to table.el, all horizontal separator lines get lost, because
-table.el uses these as cell boundaries and has no notion of horizontal lines.
-A table.el table can be converted to an Org-mode table only if it does not
-do row or column spanning. Multiline cells will become multiple cells.
-Beware, Org-mode does not test if the table can be successfully converted - it
-blindly applies a recipe that works for simple tables."
+Obviously, this only works within limits. When an Org table is converted
+to table.el, all horizontal separator lines get lost, because table.el uses
+these as cell boundaries and has no notion of horizontal lines. A table.el
+table can be converted to an Org table only if it does not do row or column
+spanning. Multiline cells will become multiple cells. Beware, Org mode
+does not test if the table can be successfully converted - it blindly
+applies a recipe that works for simple tables."
(interactive)
(require 'table)
(if (org-at-table.el-p)
- ;; convert to Org-mode table
- (let ((beg (move-marker (make-marker) (org-table-begin t)))
- (end (move-marker (make-marker) (org-table-end t))))
+ ;; convert to Org table
+ (let ((beg (copy-marker (org-table-begin t)))
+ (end (copy-marker (org-table-end t))))
(table-unrecognize-region beg end)
(goto-char beg)
(while (re-search-forward "^\\([ \t]*\\)\\+-.*\n" end t)
@@ -1808,8 +1864,8 @@ blindly applies a recipe that works for simple tables."
(goto-char beg))
(if (org-at-table-p)
;; convert to table.el table
- (let ((beg (move-marker (make-marker) (org-table-begin)))
- (end (move-marker (make-marker) (org-table-end))))
+ (let ((beg (copy-marker (org-table-begin)))
+ (end (copy-marker (org-table-end))))
;; first, get rid of all horizontal lines
(goto-char beg)
(while (re-search-forward "^\\([ \t]*\\)|-.*\n" end t)
@@ -1832,7 +1888,7 @@ blindly applies a recipe that works for simple tables."
(goto-char beg)))))
(defun org-table-transpose-table-at-point ()
- "Transpose orgmode table at point and eliminate hlines.
+ "Transpose Org table at point and eliminate hlines.
So a table like
| 1 | 2 | 4 | 5 |
@@ -1847,22 +1903,31 @@ will be transposed as
| 4 | c | g |
| 5 | d | h |
-Note that horizontal lines disappeared."
+Note that horizontal lines disappear."
(interactive)
(let* ((table (delete 'hline (org-table-to-lisp)))
- (contents (mapcar (lambda (p)
+ (dline_old (org-table-current-line))
+ (col_old (org-table-current-column))
+ (contents (mapcar (lambda (_)
(let ((tp table))
(mapcar
- (lambda (rown)
+ (lambda (_)
(prog1
(pop (car tp))
(setq tp (cdr tp))))
table)))
(car table))))
- (delete-region (org-table-begin) (org-table-end))
- (insert (mapconcat (lambda(x) (concat "| " (mapconcat 'identity x " | " ) " |\n" ))
- contents ""))
- (org-table-align)))
+ (goto-char (org-table-begin))
+ (re-search-forward "|")
+ (backward-char)
+ (delete-region (point) (org-table-end))
+ (insert (mapconcat
+ (lambda(x)
+ (concat "| " (mapconcat 'identity x " | " ) " |\n" ))
+ contents ""))
+ (org-table-goto-line col_old)
+ (org-table-goto-column dline_old))
+ (org-table-align))
;;;###autoload
(defun org-table-wrap-region (arg)
@@ -1873,7 +1938,8 @@ lines, in order to keep the table compact.
If there is an active region, and both point and mark are in the same column,
the text in the column is wrapped to minimum width for the given number of
lines. Generally, this makes the table more compact. A prefix ARG may be
-used to change the number of desired lines. For example, `C-2 \\[org-table-wrap]'
+used to change the number of desired lines. For example, \
+`C-2 \\[org-table-wrap-region]'
formats the selected text to two lines. If the region was longer than two
lines, the remaining lines remain empty. A negative prefix argument reduces
the current number of lines by that amount. The wrapped text is pasted back
@@ -1890,57 +1956,53 @@ blank, and the content is appended to the field above."
(interactive "P")
(org-table-check-inside-data-field)
(if (org-region-active-p)
- ;; There is a region: fill as a paragraph
- (let* ((beg (region-beginning))
- (cline (save-excursion (goto-char beg) (org-current-line)))
- (ccol (save-excursion (goto-char beg) (org-table-current-column)))
- nlines)
+ ;; There is a region: fill as a paragraph.
+ (let ((start (region-beginning)))
(org-table-cut-region (region-beginning) (region-end))
- (if (> (length (car org-table-clip)) 1)
- (user-error "Region must be limited to single column"))
- (setq nlines (if arg
- (if (< arg 1)
- (+ (length org-table-clip) arg)
- arg)
- (length org-table-clip)))
- (setq org-table-clip
- (mapcar 'list (org-wrap (mapconcat 'car org-table-clip " ")
- nil nlines)))
- (org-goto-line cline)
- (org-table-goto-column ccol)
+ (when (> (length (car org-table-clip)) 1)
+ (user-error "Region must be limited to single column"))
+ (let ((nlines (cond ((not arg) (length org-table-clip))
+ ((< arg 1) (+ (length org-table-clip) arg))
+ (t arg))))
+ (setq org-table-clip
+ (mapcar #'list
+ (org-wrap (mapconcat #'car org-table-clip " ")
+ nil
+ nlines))))
+ (goto-char start)
(org-table-paste-rectangle))
- ;; No region, split the current field at point
+ ;; No region, split the current field at point.
(unless (org-get-alist-option org-M-RET-may-split-line 'table)
(skip-chars-forward "^\r\n|"))
- (if arg
- ;; combine with field above
- (let ((s (org-table-blank-field))
- (col (org-table-current-column)))
- (beginning-of-line 0)
- (while (org-at-table-hline-p) (beginning-of-line 0))
- (org-table-goto-column col)
- (skip-chars-forward "^|")
- (skip-chars-backward " ")
- (insert " " (org-trim s))
- (org-table-align))
- ;; split field
- (if (looking-at "\\([^|]+\\)+|")
- (let ((s (match-string 1)))
- (replace-match " |")
- (goto-char (match-beginning 0))
- (org-table-next-row)
- (insert (org-trim s) " ")
- (org-table-align))
- (org-table-next-row)))))
+ (cond
+ (arg ; Combine with field above.
+ (let ((s (org-table-blank-field))
+ (col (org-table-current-column)))
+ (forward-line -1)
+ (while (org-at-table-hline-p) (forward-line -1))
+ (org-table-goto-column col)
+ (skip-chars-forward "^|")
+ (skip-chars-backward " ")
+ (insert " " (org-trim s))
+ (org-table-align)))
+ ((looking-at "\\([^|]+\\)+|") ; Split field.
+ (let ((s (match-string 1)))
+ (replace-match " |")
+ (goto-char (match-beginning 0))
+ (org-table-next-row)
+ (insert (org-trim s) " ")
+ (org-table-align)))
+ (t (org-table-next-row)))))
(defvar org-field-marker nil)
;;;###autoload
(defun org-table-edit-field (arg)
"Edit table field in a different window.
-This is mainly useful for fields that contain hidden parts.
-When called with a \\[universal-argument] prefix, just make the full field visible so that
-it can be edited in place."
+This is mainly useful for fields that contain hidden parts. When called
+with a `\\[universal-argument]' prefix, just make the full field \
+visible so that it can be
+edited in place."
(interactive "P")
(cond
((equal arg '(16))
@@ -1980,9 +2042,9 @@ it can be edited in place."
'(invisible t org-cwidth t display t
intangible t))
(goto-char p)
- (org-set-local 'org-finish-function 'org-table-finish-edit-field)
- (org-set-local 'org-window-configuration cw)
- (org-set-local 'org-field-marker pos)
+ (setq-local org-finish-function 'org-table-finish-edit-field)
+ (setq-local org-window-configuration cw)
+ (setq-local org-field-marker pos)
(message "Edit and finish with C-c C-c")))))
(defun org-table-finish-edit-field ()
@@ -2015,8 +2077,8 @@ current field. The mode exits automatically when the cursor leaves the
table (but see `org-table-exit-follow-field-mode-when-leaving-table')."
nil " TblFollow" nil
(if org-table-follow-field-mode
- (org-add-hook 'post-command-hook 'org-table-follow-fields-with-editor
- 'append 'local)
+ (add-hook 'post-command-hook 'org-table-follow-fields-with-editor
+ 'append 'local)
(remove-hook 'post-command-hook 'org-table-follow-fields-with-editor 'local)
(let* ((buf (get-buffer "*Org Table Edit Field*"))
(win (and buf (get-buffer-window buf))))
@@ -2091,11 +2153,10 @@ If NLAST is a number, only the NLAST fields will actually be summed."
s diff)
(format "%.0f:%02.0f:%02.0f" h m s))))
(kill-new sres)
- (if (org-called-interactively-p 'interactive)
- (message "%s"
- (substitute-command-keys
- (format "Sum of %d items: %-20s (\\[yank] will insert result into buffer)"
- (length numbers) sres))))
+ (when (called-interactively-p 'interactive)
+ (message "%s" (substitute-command-keys
+ (format "Sum of %d items: %-20s \
+\(\\[yank] will insert result into buffer)" (length numbers) sres))))
sres))))
(defun org-table-get-number-for-summing (s)
@@ -2120,57 +2181,58 @@ If NLAST is a number, only the NLAST fields will actually be summed."
(defun org-table-current-field-formula (&optional key noerror)
"Return the formula active for the current field.
-Assumes that specials are in place.
-If KEY is given, return the key to this formula.
-Otherwise return the formula preceded with \"=\" or \":=\"."
- (let* ((name (car (rassoc (list (org-current-line)
- (org-table-current-column))
- org-table-named-field-locations)))
- (col (org-table-current-column))
- (scol (int-to-string col))
- (ref (format "@%d$%d" (org-table-current-dline) col))
- (stored-list (org-table-get-stored-formulas noerror))
- (ass (or (assoc name stored-list)
- (assoc ref stored-list)
- (assoc scol stored-list))))
- (if key
- (car ass)
- (if ass (concat (if (string-match "^[0-9]+$" (car ass)) "=" ":=")
- (cdr ass))))))
+
+Assumes that table is already analyzed. If KEY is given, return
+the key to this formula. Otherwise return the formula preceded
+with \"=\" or \":=\"."
+ (let* ((line (count-lines org-table-current-begin-pos
+ (line-beginning-position)))
+ (row (org-table-line-to-dline line)))
+ (cond
+ (row
+ (let* ((col (org-table-current-column))
+ (name (car (rassoc (list line col)
+ org-table-named-field-locations)))
+ (scol (format "$%d" col))
+ (ref (format "@%d$%d" (org-table-current-dline) col))
+ (stored-list (org-table-get-stored-formulas noerror))
+ (ass (or (assoc name stored-list)
+ (assoc ref stored-list)
+ (assoc scol stored-list))))
+ (cond (key (car ass))
+ (ass (concat (if (string-match-p "^[0-9]+$" (car ass)) "=" ":=")
+ (cdr ass))))))
+ (noerror nil)
+ (t (error "No formula active for the current field")))))
(defun org-table-get-formula (&optional equation named)
"Read a formula from the minibuffer, offer stored formula as default.
When NAMED is non-nil, look for a named equation."
(let* ((stored-list (org-table-get-stored-formulas))
- (name (car (rassoc (list (org-current-line)
+ (name (car (rassoc (list (count-lines org-table-current-begin-pos
+ (line-beginning-position))
(org-table-current-column))
org-table-named-field-locations)))
- (ref (format "@%d$%d" (org-table-current-dline)
+ (ref (format "@%d$%d"
+ (org-table-current-dline)
(org-table-current-column)))
- (refass (assoc ref stored-list))
- (nameass (assoc name stored-list))
- (scol (if named
- (if (and name (not (string-match "^LR[0-9]+$" name)))
- name
- ref)
- (int-to-string (org-table-current-column))))
- (dummy (and (or nameass refass) (not named)
- (not (y-or-n-p "Replace existing field formula with column formula? " ))
- (message "Formula not replaced")))
+ (scol (cond
+ ((not named) (format "$%d" (org-table-current-column)))
+ ((and name (not (string-match "\\`LR[0-9]+\\'" name))) name)
+ (t ref)))
(name (or name ref))
(org-table-may-need-update nil)
(stored (cdr (assoc scol stored-list)))
(eq (cond
- ((and stored equation (string-match "^ *=? *$" equation))
+ ((and stored equation (string-match-p "^ *=? *$" equation))
stored)
((stringp equation)
equation)
(t (org-table-formula-from-user
(read-string
(org-table-formula-to-user
- (format "%s formula %s%s="
+ (format "%s formula %s="
(if named "Field" "Column")
- (if (member (string-to-char scol) '(?$ ?@)) "" "$")
scol))
(if stored (org-table-formula-to-user stored) "")
'org-table-formula-history
@@ -2194,25 +2256,27 @@ When NAMED is non-nil, look for a named equation."
(org-table-store-formulas stored-list))
eq))
-(defun org-table-store-formulas (alist)
- "Store the list of formulas below the current table."
- (setq alist (sort alist 'org-table-formula-less-p))
- (let ((case-fold-search t))
- (save-excursion
- (goto-char (org-table-end))
- (if (looking-at "\\([ \t]*\n\\)*[ \t]*\\(#\\+tblfm:\\)\\(.*\n?\\)")
+(defun org-table-store-formulas (alist &optional location)
+ "Store the list of formulas below the current table.
+If optional argument LOCATION is a buffer position, insert it at
+LOCATION instead."
+ (save-excursion
+ (if location
+ (progn (goto-char location) (beginning-of-line))
+ (goto-char (org-table-end)))
+ (let ((case-fold-search t))
+ (if (looking-at "\\([ \t]*\n\\)*[ \t]*\\(#\\+TBLFM:\\)\\(.*\n?\\)")
(progn
- ;; don't overwrite TBLFM, we might use text properties to store stuff
+ ;; Don't overwrite TBLFM, we might use text properties to
+ ;; store stuff.
(goto-char (match-beginning 3))
(delete-region (match-beginning 3) (match-end 0)))
(org-indent-line)
(insert (or (match-string 2) "#+TBLFM:")))
(insert " "
- (mapconcat (lambda (x)
- (concat
- (if (equal (string-to-char (car x)) ?@) "" "$")
- (car x) "=" (cdr x)))
- alist "::")
+ (mapconcat (lambda (x) (concat (car x) "=" (cdr x)))
+ (sort alist #'org-table-formula-less-p)
+ "::")
"\n"))))
(defsubst org-table-formula-make-cmp-string (a)
@@ -2241,33 +2305,47 @@ When NAMED is non-nil, look for a named equation."
(and as bs (string< as bs))))
;;;###autoload
-(defun org-table-get-stored-formulas (&optional noerror)
- "Return an alist with the stored formulas directly after current table."
- (interactive) ;; FIXME interactive?
- (let ((case-fold-search t) scol eq eq-alist strings string seen)
- (save-excursion
- (goto-char (org-table-end))
- (when (looking-at "\\([ \t]*\n\\)*[ \t]*#\\+tblfm: *\\(.*\\)")
- (setq strings (org-split-string (org-match-string-no-properties 2)
- " *:: *"))
- (while (setq string (pop strings))
- (when (string-match "\\`\\(@[-+I<>0-9.$@]+\\|@?[0-9]+\\|\\$\\([a-zA-Z0-9]+\\|[<>]+\\)\\) *= *\\(.*[^ \t]\\)" string)
- (setq scol (if (match-end 2)
- (match-string 2 string)
- (match-string 1 string))
- scol (if (member (string-to-char scol) '(?< ?>))
- (concat "$" scol) scol)
- eq (match-string 3 string)
- eq-alist (cons (cons scol eq) eq-alist))
- (if (member scol seen)
- (if noerror
- (progn
- (message "Double definition `$%s=' in TBLFM line, please fix by hand" scol)
- (ding)
- (sit-for 2))
- (user-error "Double definition `$%s=' in TBLFM line, please fix by hand" scol))
- (push scol seen))))))
- (nreverse eq-alist)))
+(defun org-table-get-stored-formulas (&optional noerror location)
+ "Return an alist with the stored formulas directly after current table.
+By default, only return active formulas, i.e., formulas located
+on the first line after the table. However, if optional argument
+LOCATION is a buffer position, consider the formulas there."
+ (save-excursion
+ (if location
+ (progn (goto-char location) (beginning-of-line))
+ (goto-char (org-table-end)))
+ (let ((case-fold-search t))
+ (when (looking-at "\\([ \t]*\n\\)*[ \t]*#\\+TBLFM: *\\(.*\\)")
+ (let ((strings (org-split-string (match-string-no-properties 2)
+ " *:: *"))
+ eq-alist seen)
+ (dolist (string strings (nreverse eq-alist))
+ (when (string-match "\\`\\(@[-+I<>0-9.$@]+\\|\\$\\([_a-zA-Z0-9]+\\|\
+[<>]+\\)\\) *= *\\(.*[^ \t]\\)"
+ string)
+ (let ((lhs
+ (let ((m (match-string 1 string)))
+ (cond
+ ((not (match-end 2)) m)
+ ;; Is it a column reference?
+ ((string-match-p "\\`$\\([0-9]+\\|[<>]+\\)\\'" m) m)
+ ;; Since named columns are not possible in
+ ;; LHS, assume this is a named field.
+ (t (match-string 2 string)))))
+ (rhs (match-string 3 string)))
+ (push (cons lhs rhs) eq-alist)
+ (cond
+ ((not (member lhs seen)) (push lhs seen))
+ (noerror
+ (message
+ "Double definition `%s=' in TBLFM line, please fix by hand"
+ lhs)
+ (ding)
+ (sit-for 2))
+ (t
+ (user-error
+ "Double definition `%s=' in TBLFM line, please fix by hand"
+ lhs)))))))))))
(defun org-table-fix-formulas (key replace &optional limit delta remove)
"Modify the equations after the table structure has been edited.
@@ -2305,83 +2383,6 @@ For all numbers larger than LIMIT, shift them by DELTA."
(message msg))))))
(forward-line))))
-(defun org-table-get-specials ()
- "Get the column names and local parameters for this table."
- (save-excursion
- (let ((beg (org-table-begin)) (end (org-table-end))
- names name fields fields1 field cnt
- c v l line col types dlines hlines last-dline)
- (setq org-table-column-names nil
- org-table-local-parameters nil
- org-table-named-field-locations nil
- org-table-current-begin-line nil
- org-table-current-begin-pos nil
- org-table-current-line-types nil
- org-table-current-ncol 0)
- (goto-char beg)
- (when (re-search-forward "^[ \t]*| *! *\\(|.*\\)" end t)
- (setq names (org-split-string (match-string 1) " *| *")
- cnt 1)
- (while (setq name (pop names))
- (setq cnt (1+ cnt))
- (if (string-match "^[a-zA-Z][_a-zA-Z0-9]*$" name)
- (push (cons name (int-to-string cnt)) org-table-column-names))))
- (setq org-table-column-names (nreverse org-table-column-names))
- (setq org-table-column-name-regexp
- (concat "\\$\\(" (mapconcat 'car org-table-column-names "\\|") "\\)\\>"))
- (goto-char beg)
- (while (re-search-forward "^[ \t]*| *\\$ *\\(|.*\\)" end t)
- (setq fields (org-split-string (match-string 1) " *| *"))
- (while (setq field (pop fields))
- (if (string-match "^\\([a-zA-Z][_a-zA-Z0-9]*\\|%\\) *= *\\(.*\\)" field)
- (push (cons (match-string 1 field) (match-string 2 field))
- org-table-local-parameters))))
- (goto-char beg)
- (while (re-search-forward "^[ \t]*| *\\([_^]\\) *\\(|.*\\)" end t)
- (setq c (match-string 1)
- fields (org-split-string (match-string 2) " *| *"))
- (save-excursion
- (beginning-of-line (if (equal c "_") 2 0))
- (setq line (org-current-line) col 1)
- (and (looking-at "^[ \t]*|[^|]*\\(|.*\\)")
- (setq fields1 (org-split-string (match-string 1) " *| *"))))
- (while (and fields1 (setq field (pop fields)))
- (setq v (pop fields1) col (1+ col))
- (when (and (stringp field) (stringp v)
- (string-match "^[a-zA-Z][_a-zA-Z0-9]*$" field))
- (push (cons field v) org-table-local-parameters)
- (push (list field line col) org-table-named-field-locations))))
- ;; Analyze the line types.
- (goto-char beg)
- (setq org-table-current-begin-line (org-current-line)
- org-table-current-begin-pos (point)
- l org-table-current-begin-line)
- (while (looking-at "[ \t]*|\\(-\\)?")
- (push (if (match-end 1) 'hline 'dline) types)
- (if (match-end 1) (push l hlines) (push l dlines))
- (beginning-of-line 2)
- (setq l (1+ l)))
- (push 'hline types) ;; add an imaginary extra hline to the end
- (setq org-table-current-line-types (apply 'vector (nreverse types))
- last-dline (car dlines)
- org-table-dlines (apply 'vector (cons nil (nreverse dlines)))
- org-table-hlines (apply 'vector (cons nil (nreverse hlines))))
- (org-goto-line last-dline)
- (let* ((l last-dline)
- (fields (org-split-string
- (buffer-substring (point-at-bol) (point-at-eol))
- "[ \t]*|[ \t]*"))
- (nfields (length fields))
- al al2)
- (setq org-table-current-ncol nfields)
- (loop for i from 1 to nfields do
- (push (list (format "LR%d" i) l i) al)
- (push (cons (format "LR%d" i) (nth (1- i) fields)) al2))
- (setq org-table-named-field-locations
- (append org-table-named-field-locations al))
- (setq org-table-local-parameters
- (append org-table-local-parameters al2))))))
-
;;;###autoload
(defun org-table-maybe-eval-formula ()
"Check if the current field starts with \"=\" or \":=\".
@@ -2394,11 +2395,8 @@ If yes, store the formula and apply it."
(when (string-match "^:?=\\(.*[^=]\\)$" field)
(setq named (equal (string-to-char field) ?:)
eq (match-string 1 field))
- (if (or (fboundp 'calc-eval)
- (equal (substring eq 0 (min 2 (length eq))) "'("))
- (org-table-eval-formula (if named '(4) nil)
- (org-table-formula-from-user eq))
- (user-error "Calc does not seem to be installed, and is needed to evaluate the formula"))))))
+ (org-table-eval-formula (and named '(4))
+ (org-table-formula-from-user eq))))))
(defvar org-recalc-commands nil
"List of commands triggering the recalculation of a line.
@@ -2424,56 +2422,199 @@ After each change, a message will be displayed indicating the meaning
of the new mark."
(interactive)
(unless (org-at-table-p) (user-error "Not at a table"))
- (let* ((marks (append (mapcar 'car org-recalc-marks) '(" ")))
- (beg (org-table-begin))
- (end (org-table-end))
- (l (org-current-line))
- (l1 (if (org-region-active-p) (org-current-line (region-beginning))))
- (l2 (if (org-region-active-p) (org-current-line (region-end))))
- (have-col
- (save-excursion
- (goto-char beg)
- (not (re-search-forward "^[ \t]*|[^-|][^|]*[^#!$*_^| \t][^|]*|" end t))))
+ (let* ((region (org-region-active-p))
+ (l1 (and region
+ (save-excursion (goto-char (region-beginning))
+ (copy-marker (line-beginning-position)))))
+ (l2 (and region
+ (save-excursion (goto-char (region-end))
+ (copy-marker (line-beginning-position)))))
+ (l (copy-marker (line-beginning-position)))
(col (org-table-current-column))
- (forcenew (car (assoc newchar org-recalc-marks)))
- epos new)
- (when l1
- (message "Change region to what mark? Type # * ! $ or SPC: ")
- (setq newchar (char-to-string (read-char-exclusive))
- forcenew (car (assoc newchar org-recalc-marks))))
- (if (and newchar (not forcenew))
- (user-error "Invalid NEWCHAR `%s' in `org-table-rotate-recalc-marks'"
- newchar))
- (if l1 (org-goto-line l1))
+ (newchar (if region
+ (char-to-string
+ (read-char-exclusive
+ "Change region to what mark? Type # * ! $ or SPC: "))
+ newchar))
+ (no-special-column
+ (save-excursion
+ (goto-char (org-table-begin))
+ (re-search-forward
+ "^[ \t]*|[^-|][^|]*[^#!$*_^| \t][^|]*|" (org-table-end) t))))
+ (when (and newchar (not (assoc newchar org-recalc-marks)))
+ (user-error "Invalid character `%s' in `org-table-rotate-recalc-marks'"
+ newchar))
+ (when l1 (goto-char l1))
(save-excursion
- (beginning-of-line 1)
+ (beginning-of-line)
(unless (looking-at org-table-dataline-regexp)
(user-error "Not at a table data line")))
- (unless have-col
+ (when no-special-column
(org-table-goto-column 1)
- (org-table-insert-column)
- (org-table-goto-column (1+ col)))
- (setq epos (point-at-eol))
+ (org-table-insert-column))
+ (let ((previous-line-end (line-end-position))
+ (newchar
+ (save-excursion
+ (beginning-of-line)
+ (cond ((not (looking-at "^[ \t]*| *\\([#!$*^_ ]\\) *|")) "#")
+ (newchar)
+ (t (cadr (member (match-string 1)
+ (append (mapcar #'car org-recalc-marks)
+ '(" ")))))))))
+ ;; Rotate mark in first row.
+ (org-table-get-field 1 (format " %s " newchar))
+ ;; Rotate marks in additional rows if a region is active.
+ (when region
+ (save-excursion
+ (forward-line)
+ (while (<= (point) l2)
+ (when (looking-at org-table-dataline-regexp)
+ (org-table-get-field 1 (format " %s " newchar)))
+ (forward-line))))
+ ;; Only align if rotation actually changed lines' length.
+ (when (/= previous-line-end (line-end-position)) (org-table-align)))
+ (goto-char l)
+ (org-table-goto-column (if no-special-column (1+ col) col))
+ (when l1 (set-marker l1 nil))
+ (when l2 (set-marker l2 nil))
+ (set-marker l nil)
+ (when (called-interactively-p 'interactive)
+ (message "%s" (cdr (assoc newchar org-recalc-marks))))))
+
+;;;###autoload
+(defun org-table-analyze ()
+ "Analyze table at point and store results.
+
+This function sets up the following dynamically scoped variables:
+
+ `org-table-column-name-regexp',
+ `org-table-column-names',
+ `org-table-current-begin-pos',
+ `org-table-current-line-types',
+ `org-table-current-ncol',
+ `org-table-dlines',
+ `org-table-hlines',
+ `org-table-local-parameters',
+ `org-table-named-field-locations'."
+ (let ((beg (org-table-begin))
+ (end (org-table-end)))
(save-excursion
- (beginning-of-line 1)
- (org-table-get-field
- 1 (if (looking-at "^[ \t]*| *\\([#!$*^_ ]\\) *|")
- (concat " "
- (setq new (or forcenew
- (cadr (member (match-string 1) marks))))
- " ")
- " # ")))
- (if (and l1 l2)
- (progn
- (org-goto-line l1)
- (while (progn (beginning-of-line 2) (not (= (org-current-line) l2)))
- (and (looking-at org-table-dataline-regexp)
- (org-table-get-field 1 (concat " " new " "))))
- (org-goto-line l1)))
- (if (not (= epos (point-at-eol))) (org-table-align))
- (org-goto-line l)
- (and (org-called-interactively-p 'interactive)
- (message "%s" (cdr (assoc new org-recalc-marks))))))
+ (goto-char beg)
+ ;; Extract column names.
+ (setq org-table-column-names nil)
+ (when (save-excursion
+ (re-search-forward "^[ \t]*| *! *\\(|.*\\)" end t))
+ (let ((c 1))
+ (dolist (name (org-split-string (match-string 1) " *| *"))
+ (cl-incf c)
+ (when (string-match "\\`[a-zA-Z][_a-zA-Z0-9]*\\'" name)
+ (push (cons name (int-to-string c)) org-table-column-names)))))
+ (setq org-table-column-names (nreverse org-table-column-names))
+ (setq org-table-column-name-regexp
+ (format "\\$\\(%s\\)\\>"
+ (regexp-opt (mapcar #'car org-table-column-names) t)))
+ ;; Extract local parameters.
+ (setq org-table-local-parameters nil)
+ (save-excursion
+ (while (re-search-forward "^[ \t]*| *\\$ *\\(|.*\\)" end t)
+ (dolist (field (org-split-string (match-string 1) " *| *"))
+ (when (string-match
+ "\\`\\([a-zA-Z][_a-zA-Z0-9]*\\|%\\) *= *\\(.*\\)" field)
+ (push (cons (match-string 1 field) (match-string 2 field))
+ org-table-local-parameters)))))
+ ;; Update named fields locations. We minimize `count-lines'
+ ;; processing by storing last known number of lines in LAST.
+ (setq org-table-named-field-locations nil)
+ (save-excursion
+ (let ((last (cons (point) 0)))
+ (while (re-search-forward "^[ \t]*| *\\([_^]\\) *\\(|.*\\)" end t)
+ (let ((c (match-string 1))
+ (fields (org-split-string (match-string 2) " *| *")))
+ (save-excursion
+ (forward-line (if (equal c "_") 1 -1))
+ (let ((fields1
+ (and (looking-at "^[ \t]*|[^|]*\\(|.*\\)")
+ (org-split-string (match-string 1) " *| *")))
+ (line (cl-incf (cdr last) (count-lines (car last) (point))))
+ (col 1))
+ (setcar last (point)) ; Update last known position.
+ (while (and fields fields1)
+ (let ((field (pop fields))
+ (v (pop fields1)))
+ (cl-incf col)
+ (when (and (stringp field)
+ (stringp v)
+ (string-match "\\`[a-zA-Z][_a-zA-Z0-9]*\\'"
+ field))
+ (push (cons field v) org-table-local-parameters)
+ (push (list field line col)
+ org-table-named-field-locations))))))))))
+ ;; Re-use existing markers when possible.
+ (if (markerp org-table-current-begin-pos)
+ (move-marker org-table-current-begin-pos (point))
+ (setq org-table-current-begin-pos (point-marker)))
+ ;; Analyze the line types.
+ (let ((l 0) hlines dlines types)
+ (while (looking-at "[ \t]*|\\(-\\)?")
+ (push (if (match-end 1) 'hline 'dline) types)
+ (if (match-end 1) (push l hlines) (push l dlines))
+ (forward-line)
+ (cl-incf l))
+ (push 'hline types) ; Add an imaginary extra hline to the end.
+ (setq org-table-current-line-types (apply #'vector (nreverse types)))
+ (setq org-table-dlines (apply #'vector (cons nil (nreverse dlines))))
+ (setq org-table-hlines (apply #'vector (cons nil (nreverse hlines)))))
+ ;; Get the number of columns from the first data line in table.
+ (goto-char beg)
+ (forward-line (aref org-table-dlines 1))
+ (let* ((fields
+ (org-split-string
+ (buffer-substring (line-beginning-position) (line-end-position))
+ "[ \t]*|[ \t]*"))
+ (nfields (length fields))
+ al al2)
+ (setq org-table-current-ncol nfields)
+ (let ((last-dline
+ (aref org-table-dlines (1- (length org-table-dlines)))))
+ (dotimes (i nfields)
+ (let ((column (1+ i)))
+ (push (list (format "LR%d" column) last-dline column) al)
+ (push (cons (format "LR%d" column) (nth i fields)) al2))))
+ (setq org-table-named-field-locations
+ (append org-table-named-field-locations al))
+ (setq org-table-local-parameters
+ (append org-table-local-parameters al2))))))
+
+(defun org-table-goto-field (ref &optional create-column-p)
+ "Move point to a specific field in the current table.
+
+REF is either the name of a field its absolute reference, as
+a string. No column is created unless CREATE-COLUMN-P is
+non-nil. If it is a function, it is called with the column
+number as its argument as is used as a predicate to know if the
+column can be created.
+
+This function assumes the table is already analyzed (i.e., using
+`org-table-analyze')."
+ (let* ((coordinates
+ (cond
+ ((cdr (assoc ref org-table-named-field-locations)))
+ ((string-match "\\`@\\([1-9][0-9]*\\)\\$\\([1-9][0-9]*\\)\\'" ref)
+ (list (condition-case nil
+ (aref org-table-dlines
+ (string-to-number (match-string 1 ref)))
+ (error (user-error "Invalid row number in %s" ref)))
+ (string-to-number (match-string 2 ref))))
+ (t (user-error "Unknown field: %s" ref))))
+ (line (car coordinates))
+ (column (nth 1 coordinates))
+ (create-new-column (if (functionp create-column-p)
+ (funcall create-column-p column)
+ create-column-p)))
+ (when coordinates
+ (goto-char org-table-current-begin-pos)
+ (forward-line line)
+ (org-table-goto-column column nil create-new-column))))
;;;###autoload
(defun org-table-maybe-recalculate-line ()
@@ -2481,7 +2622,7 @@ of the new mark."
(interactive)
(and org-table-allow-automatic-line-recalculation
(not (and (memq last-command org-recalc-commands)
- (equal org-last-recalc-line (org-current-line))))
+ (eq org-last-recalc-line (line-beginning-position))))
(save-excursion (beginning-of-line 1)
(looking-at org-table-auto-recalculate-regexp))
(org-table-recalculate) t))
@@ -2505,20 +2646,18 @@ of the new mark."
suppress-store suppress-analysis)
"Replace the table field value at the cursor by the result of a calculation.
-This function makes use of Dave Gillespie's Calc package, in my view the
-most exciting program ever written for GNU Emacs. So you need to have Calc
-installed in order to use this function.
-
In a table, this command replaces the value in the current field with the
result of a formula. It also installs the formula as the \"current\" column
formula, by storing it in a special line below the table. When called
-with a `C-u' prefix, the current field must be a named field, and the
-formula is installed as valid in only this specific field.
+with a `\\[universal-argument]' prefix the formula is installed as a \
+field formula.
-When called with two `C-u' prefixes, insert the active equation
-for the field back into the current field, so that it can be
-edited there. This is useful in order to use \\[org-table-show-reference]
-to check the referenced fields.
+When called with a `\\[universal-argument] \\[universal-argument]' prefix, \
+insert the active equation for the field
+back into the current field, so that it can be edited there. This is \
+useful
+in order to use \\`\\[org-table-show-reference]' to \
+check the referenced fields.
When called, the command first prompts for a formula, which is read in
the minibuffer. Previously entered formulas are available through the
@@ -2527,7 +2666,7 @@ These stored formulas are adapted correctly when moving, inserting, or
deleting columns with the corresponding commands.
The formula can be any algebraic expression understood by the Calc package.
-For details, see the Org-mode manual.
+For details, see the Org mode manual.
This function can also be called from Lisp programs and offers
additional arguments: EQUATION can be the formula to apply. If this
@@ -2537,13 +2676,13 @@ SUPPRESS-CONST suppresses the interpretation of constants in the
formula, assuming that this has been done already outside the function.
SUPPRESS-STORE means the formula should not be stored, either because
it is already stored, or because it is a modified equation that should
-not overwrite the stored one."
+not overwrite the stored one. SUPPRESS-ANALYSIS prevents any call to
+`org-table-analyze'."
(interactive "P")
(org-table-check-inside-data-field)
- (or suppress-analysis (org-table-get-specials))
+ (or suppress-analysis (org-table-analyze))
(if (equal arg '(16))
(let ((eq (org-table-current-field-formula)))
- (or eq (user-error "No equation active for current field"))
(org-table-get-field nil eq)
(org-table-align)
(setq org-table-may-need-update t))
@@ -2557,7 +2696,7 @@ not overwrite the stored one."
(org-table-get-formula equation (equal arg '(4)))))
(n0 (org-table-current-column))
(org-tbl-calc-modes (copy-sequence org-calc-default-modes))
- (numbers nil) ; was a variable, now fixed default
+ (numbers nil) ; was a variable, now fixed default
(keep-empty nil)
n form form0 formrpl formrg bw fmt x ev orig c lispp literal
duration duration-output-format)
@@ -2603,12 +2742,15 @@ not overwrite the stored one."
(setq fmt (replace-match "" t t fmt)))
(unless (string-match "\\S-" fmt)
(setq fmt nil))))
- (if (and (not suppress-const) org-table-formula-use-constants)
- (setq formula (org-table-formula-substitute-names formula)))
+ (when (and (not suppress-const) org-table-formula-use-constants)
+ (setq formula (org-table-formula-substitute-names formula)))
(setq orig (or (get-text-property 1 :orig-formula formula) "?"))
+ (setq formula (org-table-formula-handle-first/last-rc formula))
(while (> ndown 0)
(setq fields (org-split-string
- (buffer-substring-no-properties (point-at-bol) (point-at-eol))
+ (org-trim
+ (buffer-substring-no-properties
+ (line-beginning-position) (line-end-position)))
" *| *"))
;; replace fields with duration values if relevant
(if duration
@@ -2641,9 +2783,10 @@ not overwrite the stored one."
t t form)))
;; Check for old vertical references
- (setq form (org-table-rewrite-old-row-references form))
+ (org-table--error-on-old-row-references form)
;; Insert remote references
- (while (string-match "\\ (length (match-string 0 form)) 1))
- (setq formrg (save-match-data
- (org-table-get-range (match-string 0 form) nil n0)))
+ (setq formrg
+ (save-match-data
+ (org-table-get-range
+ (match-string 0 form) org-table-current-begin-pos n0)))
(setq formrpl
(save-match-data
(org-table-make-reference
@@ -2676,15 +2821,20 @@ not overwrite the stored one."
(string-match (regexp-quote form) formrpl)))
(setq form (replace-match formrpl t t form))
(user-error "Spreadsheet error: invalid reference \"%s\"" form)))
- ;; Insert simple ranges
- (while (string-match "\\$\\([0-9]+\\)\\.\\.\\$\\([0-9]+\\)" form)
+ ;; Insert simple ranges, i.e. included in the current row.
+ (while (string-match
+ "\\$\\(\\([-+]\\)?[0-9]+\\)\\.\\.\\$\\(\\([-+]\\)?[0-9]+\\)"
+ form)
(setq form
(replace-match
(save-match-data
(org-table-make-reference
- (org-sublist
- fields (string-to-number (match-string 1 form))
- (string-to-number (match-string 2 form)))
+ (cl-subseq fields
+ (+ (if (match-end 2) n0 0)
+ (string-to-number (match-string 1 form))
+ -1)
+ (+ (if (match-end 4) n0 0)
+ (string-to-number (match-string 3 form))))
keep-empty numbers lispp))
t t form)))
(setq form0 form)
@@ -2692,14 +2842,16 @@ not overwrite the stored one."
(while (string-match "\\$\\(\\([-+]\\)?[0-9]+\\)" form)
(setq n (+ (string-to-number (match-string 1 form))
(if (match-end 2) n0 0))
- x (nth (1- (if (= n 0) n0 (max n 1))) fields))
- (unless x (user-error "Invalid field specifier \"%s\""
- (match-string 0 form)))
- (setq form (replace-match
- (save-match-data
- (org-table-make-reference
- x keep-empty numbers lispp))
- t t form)))
+ x (nth (1- (if (= n 0) n0 (max n 1))) fields)
+ formrpl (save-match-data
+ (org-table-make-reference
+ x keep-empty numbers lispp)))
+ (when (or (not x)
+ (save-match-data
+ (string-match (regexp-quote formula) formrpl)))
+ (user-error "Invalid field specifier \"%s\""
+ (match-string 0 form)))
+ (setq form (replace-match formrpl t t form)))
(if lispp
(setq ev (condition-case nil
@@ -2709,20 +2861,23 @@ not overwrite the stored one."
ev (if duration (org-table-time-seconds-to-string
(string-to-number ev)
duration-output-format) ev))
- (or (fboundp 'calc-eval)
- (user-error "Calc does not seem to be installed, and is needed to evaluate the formula"))
- ;; Use <...> time-stamps so that Calc can handle them
- (while (string-match (concat "\\[" org-ts-regexp1 "\\]") form)
- (setq form (replace-match "<\\1>" nil nil form)))
- ;; I18n-ize local time-stamps by setting (system-time-locale "C")
- (when (string-match org-ts-regexp2 form)
- (let* ((ts (match-string 0 form))
- (tsp (apply 'encode-time (save-match-data (org-parse-time-string ts))))
- (system-time-locale "C")
- (tf (or (and (save-match-data (string-match "[0-9]\\{1,2\\}:[0-9]\\{2\\}" ts))
- (cdr org-time-stamp-formats))
- (car org-time-stamp-formats))))
- (setq form (replace-match (format-time-string tf tsp) t t form))))
+
+ ;; Use <...> time-stamps so that Calc can handle them.
+ (setq form
+ (replace-regexp-in-string org-ts-regexp-inactive "<\\1>" form))
+ ;; Internationalize local time-stamps by setting locale to
+ ;; "C".
+ (setq form
+ (replace-regexp-in-string
+ org-ts-regexp
+ (lambda (ts)
+ (let ((system-time-locale "C"))
+ (format-time-string
+ (org-time-stamp-format
+ (string-match-p "[0-9]\\{1,2\\}:[0-9]\\{2\\}" ts))
+ (apply #'encode-time
+ (save-match-data (org-parse-time-string ts))))))
+ form t t))
(setq ev (if (and duration (string-match "^[0-9]+:[0-9]+\\(?::[0-9]+\\)?$" form))
form
@@ -2742,7 +2897,7 @@ Orig: %s
$xyz-> %s
@r$c-> %s
$1-> %s\n" orig formula form0 form))
- (if (listp ev)
+ (if (consp ev)
(princ (format " %s^\nError: %s"
(make-string (car ev) ?\-) (nth 1 ev)))
(princ (format "Result: %s\nFormat: %s\nFinal: %s"
@@ -2750,14 +2905,14 @@ $1-> %s\n" orig formula form0 form))
(if fmt (format fmt (string-to-number ev)) ev)))))
(setq bw (get-buffer-window "*Substitution History*"))
(org-fit-window-to-buffer bw)
- (unless (and (org-called-interactively-p 'any) (not ndown))
+ (unless (and (called-interactively-p 'any) (not ndown))
(unless (let (inhibit-redisplay)
(y-or-n-p "Debugging Formula. Continue to next? "))
(org-table-align)
(user-error "Abort"))
(delete-window bw)
(message "")))
- (if (listp ev) (setq fmt nil ev "#ERROR"))
+ (when (consp ev) (setq fmt nil ev "#ERROR"))
(org-table-justify-field-maybe
(format org-table-formula-field-format
(if fmt (format fmt (string-to-number ev)) ev)))
@@ -2776,146 +2931,152 @@ $1-> %s\n" orig formula form0 form))
(defun org-table-get-range (desc &optional tbeg col highlight corners-only)
"Get a calc vector from a column, according to descriptor DESC.
+
Optional arguments TBEG and COL can give the beginning of the table and
the current column, to avoid unnecessary parsing.
HIGHLIGHT means just highlight the range.
When CORNERS-ONLY is set, only return the corners of the range as
-a list (line1 column1 line2 column2) where line1 and line2 are line numbers
-in the buffer and column1 and column2 are table column numbers."
- (if (not (equal (string-to-char desc) ?@))
- (setq desc (concat "@" desc)))
- (save-excursion
- (or tbeg (setq tbeg (org-table-begin)))
- (or col (setq col (org-table-current-column)))
- (let ((thisline (org-current-line))
- beg end c1 c2 r1 r2 rangep tmp)
- (unless (string-match org-table-range-regexp desc)
- (user-error "Invalid table range specifier `%s'" desc))
- (setq rangep (match-end 3)
- r1 (and (match-end 1) (match-string 1 desc))
- r2 (and (match-end 4) (match-string 4 desc))
- c1 (and (match-end 2) (substring (match-string 2 desc) 1))
- c2 (and (match-end 5) (substring (match-string 5 desc) 1)))
-
- (and c1 (setq c1 (+ (string-to-number c1)
- (if (memq (string-to-char c1) '(?- ?+)) col 0))))
- (and c2 (setq c2 (+ (string-to-number c2)
- (if (memq (string-to-char c2) '(?- ?+)) col 0))))
- (if (equal r1 "") (setq r1 nil))
- (if (equal r2 "") (setq r2 nil))
- (if r1 (setq r1 (org-table-get-descriptor-line r1)))
- (if r2 (setq r2 (org-table-get-descriptor-line r2)))
- ; (setq r2 (or r2 r1) c2 (or c2 c1))
- (if (not r1) (setq r1 thisline))
- (if (not r2) (setq r2 thisline))
- (if (or (not c1) (= 0 c1)) (setq c1 col))
- (if (or (not c2) (= 0 c2)) (setq c2 col))
- (if (and (not corners-only)
- (or (not rangep) (and (= r1 r2) (= c1 c2))))
- ;; just one field
- (progn
- (org-goto-line r1)
- (while (not (looking-at org-table-dataline-regexp))
- (beginning-of-line 2))
- (prog1 (org-trim (org-table-get-field c1))
- (if highlight (org-table-highlight-rectangle (point) (point)))))
- ;; A range, return a vector
- ;; First sort the numbers to get a regular rectangle
- (if (< r2 r1) (setq tmp r1 r1 r2 r2 tmp))
- (if (< c2 c1) (setq tmp c1 c1 c2 c2 tmp))
- (if corners-only
- ;; Only return the corners of the range
- (list r1 c1 r2 c2)
- ;; Copy the range values into a list
- (org-goto-line r1)
- (while (not (looking-at org-table-dataline-regexp))
- (beginning-of-line 2))
- (org-table-goto-column c1)
- (setq beg (point))
- (org-goto-line r2)
- (while (not (looking-at org-table-dataline-regexp))
- (beginning-of-line 0))
- (org-table-goto-column c2)
- (setq end (point))
- (if highlight
- (org-table-highlight-rectangle
- beg (progn (skip-chars-forward "^|\n") (point))))
- ;; return string representation of calc vector
- (mapcar 'org-trim
- (apply 'append (org-table-copy-region beg end))))))))
-
-(defun org-table-get-descriptor-line (desc &optional cline bline table)
- "Analyze descriptor DESC and retrieve the corresponding line number.
-The cursor is currently in line CLINE, the table begins in line BLINE,
-and TABLE is a vector with line types."
- (if (string-match "^[0-9]+$" desc)
+a list (line1 column1 line2 column2) where line1 and line2 are
+line numbers relative to beginning of table, or TBEG, and column1
+and column2 are table column numbers."
+ (let* ((desc (if (string-match-p "\\`\\$[0-9]+\\.\\.\\$[0-9]+\\'" desc)
+ (replace-regexp-in-string "\\$" "@0$" desc)
+ desc))
+ (col (or col (org-table-current-column)))
+ (tbeg (or tbeg (org-table-begin)))
+ (thisline (count-lines tbeg (line-beginning-position))))
+ (unless (string-match org-table-range-regexp desc)
+ (user-error "Invalid table range specifier `%s'" desc))
+ (let ((rangep (match-end 3))
+ (r1 (let ((r (and (match-end 1) (match-string 1 desc))))
+ (or (save-match-data
+ (and (org-string-nw-p r)
+ (org-table--descriptor-line r thisline)))
+ thisline)))
+ (r2 (let ((r (and (match-end 4) (match-string 4 desc))))
+ (or (save-match-data
+ (and (org-string-nw-p r)
+ (org-table--descriptor-line r thisline)))
+ thisline)))
+ (c1 (let ((c (and (match-end 2) (substring (match-string 2 desc) 1))))
+ (if (or (not c) (= (string-to-number c) 0)) col
+ (+ (string-to-number c)
+ (if (memq (string-to-char c) '(?- ?+)) col 0)))))
+ (c2 (let ((c (and (match-end 5) (substring (match-string 5 desc) 1))))
+ (if (or (not c) (= (string-to-number c) 0)) col
+ (+ (string-to-number c)
+ (if (memq (string-to-char c) '(?- ?+)) col 0))))))
+ (save-excursion
+ (if (and (not corners-only)
+ (or (not rangep) (and (= r1 r2) (= c1 c2))))
+ ;; Just one field.
+ (progn
+ (forward-line (- r1 thisline))
+ (while (not (looking-at org-table-dataline-regexp))
+ (forward-line))
+ (prog1 (org-trim (org-table-get-field c1))
+ (when highlight (org-table-highlight-rectangle))))
+ ;; A range, return a vector. First sort the numbers to get
+ ;; a regular rectangle.
+ (let ((first-row (min r1 r2))
+ (last-row (max r1 r2))
+ (first-column (min c1 c2))
+ (last-column (max c1 c2)))
+ (if corners-only (list first-row first-column last-row last-column)
+ ;; Copy the range values into a list.
+ (forward-line (- first-row thisline))
+ (while (not (looking-at org-table-dataline-regexp))
+ (forward-line)
+ (cl-incf first-row))
+ (org-table-goto-column first-column)
+ (let ((beg (point)))
+ (forward-line (- last-row first-row))
+ (while (not (looking-at org-table-dataline-regexp))
+ (forward-line -1))
+ (org-table-goto-column last-column)
+ (let ((end (point)))
+ (when highlight
+ (org-table-highlight-rectangle
+ beg (progn (skip-chars-forward "^|\n") (point))))
+ ;; Return string representation of calc vector.
+ (mapcar #'org-trim
+ (apply #'append
+ (org-table-copy-region beg end))))))))))))
+
+(defun org-table--descriptor-line (desc cline)
+ "Return relative line number corresponding to descriptor DESC.
+The cursor is currently in relative line number CLINE."
+ (if (string-match "\\`[0-9]+\\'" desc)
(aref org-table-dlines (string-to-number desc))
- (setq cline (or cline (org-current-line))
- bline (or bline org-table-current-begin-line)
- table (or table org-table-current-line-types))
- (if (or
- (not (string-match "^\\(\\([-+]\\)?\\(I+\\)\\)?\\(\\([-+]\\)?\\([0-9]+\\)\\)?" desc))
- ;; 1 2 3 4 5 6
- (and (not (match-end 3)) (not (match-end 6)))
- (and (match-end 3) (match-end 6) (not (match-end 5))))
- (user-error "Invalid row descriptor `%s'" desc))
- (let* ((hdir (and (match-end 2) (match-string 2 desc)))
- (hn (if (match-end 3) (- (match-end 3) (match-beginning 3)) nil))
- (odir (and (match-end 5) (match-string 5 desc)))
- (on (if (match-end 6) (string-to-number (match-string 6 desc))))
- (i (- cline bline))
+ (when (or (not (string-match
+ "^\\(\\([-+]\\)?\\(I+\\)\\)?\\(\\([-+]\\)?\\([0-9]+\\)\\)?"
+ ;; 1 2 3 4 5 6
+ desc))
+ (and (not (match-end 3)) (not (match-end 6)))
+ (and (match-end 3) (match-end 6) (not (match-end 5))))
+ (user-error "Invalid row descriptor `%s'" desc))
+ (let* ((hn (and (match-end 3) (- (match-end 3) (match-beginning 3))))
+ (hdir (match-string 2 desc))
+ (odir (match-string 5 desc))
+ (on (and (match-end 6) (string-to-number (match-string 6 desc))))
(rel (and (match-end 6)
(or (and (match-end 1) (not (match-end 3)))
(match-end 5)))))
- (if (and hn (not hdir))
- (progn
- (setq i 0 hdir "+")
- (if (eq (aref table 0) 'hline) (setq hn (1- hn)))))
- (if (and (not hn) on (not odir))
- (user-error "Should never happen");;(aref org-table-dlines on)
- (if (and hn (> hn 0))
- (setq i (org-table-find-row-type table i 'hline (equal hdir "-")
- nil hn cline desc)))
- (if on
- (setq i (org-table-find-row-type table i 'dline (equal odir "-")
- rel on cline desc)))
- (+ bline i)))))
-
-(defun org-table-find-row-type (table i type backwards relative n cline desc)
- "FIXME: Needs more documentation."
- (let ((l (length table)))
- (while (> n 0)
- (while (and (setq i (+ i (if backwards -1 1)))
- (>= i 0) (< i l)
- (not (eq (aref table i) type))
- (if (and relative (eq (aref table i) 'hline))
- (cond
- ((eq org-table-relative-ref-may-cross-hline t) t)
- ((eq org-table-relative-ref-may-cross-hline 'error)
- (user-error "Row descriptor %s used in line %d crosses hline" desc cline))
- (t (setq i (- i (if backwards -1 1))
- n 1)
- nil))
- t)))
- (setq n (1- n)))
- (if (or (< i 0) (>= i l))
- (user-error "Row descriptor %s used in line %d leads outside table"
- desc cline)
- i)))
-
-(defun org-table-rewrite-old-row-references (s)
- (if (string-match "&[-+0-9I]" s)
- (user-error "Formula contains old &row reference, please rewrite using @-syntax")
- s))
+ (when (and hn (not hdir))
+ (setq cline 0)
+ (setq hdir "+")
+ (when (eq (aref org-table-current-line-types 0) 'hline) (cl-decf hn)))
+ (when (and (not hn) on (not odir)) (user-error "Should never happen"))
+ (when hn
+ (setq cline
+ (org-table--row-type 'hline hn cline (equal hdir "-") nil desc)))
+ (when on
+ (setq cline
+ (org-table--row-type 'dline on cline (equal odir "-") rel desc)))
+ cline)))
+
+(defun org-table--row-type (type n i backwards relative desc)
+ "Return relative line of Nth row with type TYPE.
+Search starts from relative line I. When BACKWARDS in non-nil,
+look before I. When RELATIVE is non-nil, the reference is
+relative. DESC is the original descriptor that started the
+search, as a string."
+ (let ((l (length org-table-current-line-types)))
+ (catch :exit
+ (dotimes (_ n)
+ (while (and (cl-incf i (if backwards -1 1))
+ (>= i 0)
+ (< i l)
+ (not (eq (aref org-table-current-line-types i) type))
+ ;; We are going to cross a hline. Check if this is
+ ;; an authorized move.
+ (cond
+ ((not relative))
+ ((not (eq (aref org-table-current-line-types i) 'hline)))
+ ((eq org-table-relative-ref-may-cross-hline t))
+ ((eq org-table-relative-ref-may-cross-hline 'error)
+ (user-error "Row descriptor %s crosses hline" desc))
+ (t (cl-decf i (if backwards -1 1)) ; Step back.
+ (throw :exit nil)))))))
+ (cond ((or (< i 0) (>= i l))
+ (user-error "Row descriptor %s leads outside table" desc))
+ ;; The last hline doesn't exist. Instead, point to last row
+ ;; in table.
+ ((= i (1- l)) (1- i))
+ (t i))))
+
+(defun org-table--error-on-old-row-references (s)
+ (when (string-match "&[-+0-9I]" s)
+ (user-error "Formula contains old &row reference, please rewrite using @-syntax")))
(defun org-table-make-reference (elements keep-empty numbers lispp)
"Convert list ELEMENTS to something appropriate to insert into formula.
KEEP-EMPTY indicated to keep empty fields, default is to skip them.
NUMBERS indicates that everything should be converted to numbers.
LISPP non-nil means to return something appropriate for a Lisp
-list, 'literal is for the format specifier L."
+list, `literal' is for the format specifier L."
;; Calc nan (not a number) is used for the conversion of the empty
;; field to a reference for several reasons: (i) It is accepted in a
;; Calc formula (e. g. "" or "()" would result in a Calc error).
@@ -2961,162 +3122,185 @@ list, 'literal is for the format specifier L."
elements
",") "]"))))
-;;;###autoload
-(defun org-table-set-constants ()
- "Set `org-table-formula-constants-local' in the current buffer."
- (let (cst consts const-str)
- (save-excursion
- (goto-char (point-min))
- (while (re-search-forward "^[ \t]*#\\+CONSTANTS: \\(.*\\)" nil t)
- (setq const-str (substring-no-properties (match-string 1)))
- (setq consts (append consts (org-split-string const-str "[ \t]+")))
- (when consts
- (let (e)
- (while (setq e (pop consts))
- (when (string-match "^\\([a-zA-Z0][_a-zA-Z0-9]*\\)=\\(.*\\)" e)
- (if (assoc-string (match-string 1 e) cst)
- (setq cst (delete (assoc-string (match-string 1 e) cst) cst)))
- (push (cons (match-string 1 e) (match-string 2 e)) cst)))
- (setq org-table-formula-constants-local cst)))))))
+(defun org-table-message-once-per-second (t1 &rest args)
+ "If there has been more than one second since T1, display message.
+ARGS are passed as arguments to the `message' function. Returns
+current time if a message is printed, otherwise returns T1. If
+T1 is nil, always messages."
+ (let ((curtime (current-time)))
+ (if (or (not t1) (< 0 (nth 1 (time-subtract curtime t1))))
+ (progn (apply 'message args)
+ curtime)
+ t1)))
;;;###autoload
(defun org-table-recalculate (&optional all noalign)
"Recalculate the current table line by applying all stored formulas.
+
With prefix arg ALL, do this for all lines in the table.
-With the prefix argument ALL is `(16)' \
-\(a double \\[universal-prefix] \\[universal-prefix] prefix), or if
-it is the symbol `iterate', recompute the table until it no longer changes.
+
+When called with a `\\[universal-argument] \\[universal-argument]' prefix, or \
+if ALL is the symbol `iterate',
+recompute the table until it no longer changes.
+
If NOALIGN is not nil, do not re-align the table after the computations
are done. This is typically used internally to save time, if it is
known that the table will be realigned a little later anyway."
(interactive "P")
- (or (memq this-command org-recalc-commands)
- (setq org-recalc-commands (cons this-command org-recalc-commands)))
+ (unless (memq this-command org-recalc-commands)
+ (push this-command org-recalc-commands))
(unless (org-at-table-p) (user-error "Not at a table"))
(if (or (eq all 'iterate) (equal all '(16)))
(org-table-iterate)
- (org-table-get-specials)
+ (org-table-analyze)
(let* ((eqlist (sort (org-table-get-stored-formulas)
(lambda (a b) (string< (car a) (car b)))))
- (eqlist1 (copy-sequence eqlist))
(inhibit-redisplay (not debug-on-error))
(line-re org-table-dataline-regexp)
- (thisline (org-current-line))
- (thiscol (org-table-current-column))
- seen-fields lhs1
- beg end entry eqlnum eqlname eqlname1 eql (cnt 0) eq a name name1)
- ;; Insert constants in all formulas
- (setq eqlist
- (mapcar (lambda (x)
- (when (string-match "\\`$[<>]" (car x))
- (setq lhs1 (car x))
- (setq x (cons (substring
- (org-table-formula-handle-first/last-rc
- (car x)) 1)
- (cdr x)))
- (if (assoc (car x) eqlist1)
- (user-error "\"%s=\" formula tries to overwrite existing formula for column %s"
- lhs1 (car x))))
- (cons
- (org-table-formula-handle-first/last-rc (car x))
- (org-table-formula-substitute-names
- (org-table-formula-handle-first/last-rc (cdr x)))))
- eqlist))
- ;; Split the equation list
- (while (setq eq (pop eqlist))
- (if (<= (string-to-char (car eq)) ?9)
- (push eq eqlnum)
- (push eq eqlname)))
- (setq eqlnum (nreverse eqlnum) eqlname (nreverse eqlname))
- ;; Expand ranges in lhs of formulas
- (setq eqlname (org-table-expand-lhs-ranges eqlname))
-
- ;; Get the correct line range to process
- (if all
- (progn
- (setq end (move-marker (make-marker) (1+ (org-table-end))))
- (goto-char (setq beg (org-table-begin)))
- (if (re-search-forward org-table-calculate-mark-regexp end t)
- ;; This is a table with marked lines, compute selected lines
- (setq line-re org-table-recalculate-regexp)
- ;; Move forward to the first non-header line
- (if (and (re-search-forward org-table-dataline-regexp end t)
- (re-search-forward org-table-hline-regexp end t)
- (re-search-forward org-table-dataline-regexp end t))
- (setq beg (match-beginning 0))
- nil))) ;; just leave beg where it is
- (setq beg (point-at-bol)
- end (move-marker (make-marker) (1+ (point-at-eol)))))
- (goto-char beg)
- (and all (message "Re-applying formulas to full table..."))
-
- ;; First find the named fields, and mark them untouchable.
- ;; Also check if several field/range formulas try to set the same field.
- (remove-text-properties beg end '(org-untouchable t))
- (while (setq eq (pop eqlname))
- (setq name (car eq)
- a (assoc name org-table-named-field-locations))
- (setq name1 name)
- (if a (setq name1 (format "@%d$%d" (org-table-line-to-dline (nth 1 a))
- (nth 2 a))))
- (when (member name1 seen-fields)
- (user-error "Several field/range formulas try to set %s" name1))
- (push name1 seen-fields)
-
- (and (not a)
- (string-match "@\\([0-9]+\\)\\$\\([0-9]+\\)" name)
- (setq a (list name
- (condition-case nil
- (aref org-table-dlines
- (string-to-number (match-string 1 name)))
- (error (user-error "Invalid row number in %s"
- name)))
- (string-to-number (match-string 2 name)))))
- (when (and a (or all (equal (nth 1 a) thisline)))
- (message "Re-applying formula to field: %s" name)
- (org-goto-line (nth 1 a))
- (org-table-goto-column (nth 2 a))
- (push (append a (list (cdr eq))) eqlname1)
- (org-table-put-field-property :org-untouchable t)))
- (setq eqlname1 (nreverse eqlname1))
-
- ;; Now evaluate the column formulas, but skip fields covered by
- ;; field formulas
- (goto-char beg)
- (while (re-search-forward line-re end t)
- (unless (string-match "^ *[_^!$/] *$" (org-table-get-field 1))
- ;; Unprotected line, recalculate
- (and all (message "Re-applying formulas to full table...(line %d)"
- (setq cnt (1+ cnt))))
- (setq org-last-recalc-line (org-current-line))
- (setq eql eqlnum)
- (while (setq entry (pop eql))
- (org-goto-line org-last-recalc-line)
- (org-table-goto-column (string-to-number (car entry)) nil 'force)
- (unless (get-text-property (point) :org-untouchable)
- (org-table-eval-formula nil (cdr entry)
- 'noalign 'nocst 'nostore 'noanalysis)))))
-
- ;; Now evaluate the field formulas
- (while (setq eq (pop eqlname1))
- (message "Re-applying formula to field: %s" (car eq))
- (org-goto-line (nth 1 eq))
- (org-table-goto-column (nth 2 eq))
- (org-table-eval-formula nil (nth 3 eq) 'noalign 'nocst
- 'nostore 'noanalysis))
-
- (org-goto-line thisline)
- (org-table-goto-column thiscol)
- (remove-text-properties (point-min) (point-max) '(org-untouchable t))
- (or noalign (and org-table-may-need-update (org-table-align))
- (and all (message "Re-applying formulas to %d lines...done" cnt)))
-
- ;; back to initial position
- (message "Re-applying formulas...done")
- (org-goto-line thisline)
- (org-table-goto-column thiscol)
- (or noalign (and org-table-may-need-update (org-table-align))
- (and all (message "Re-applying formulas...done"))))))
+ (log-first-time (current-time))
+ (log-last-time log-first-time)
+ (cnt 0)
+ beg end eqlcol eqlfield)
+ ;; Insert constants in all formulas.
+ (when eqlist
+ (org-table-save-field
+ ;; Expand equations, then split the equation list between
+ ;; column formulas and field formulas.
+ (dolist (eq eqlist)
+ (let* ((rhs (org-table-formula-substitute-names
+ (org-table-formula-handle-first/last-rc (cdr eq))))
+ (old-lhs (car eq))
+ (lhs
+ (org-table-formula-handle-first/last-rc
+ (cond
+ ((string-match "\\`@-?I+" old-lhs)
+ (user-error "Can't assign to hline relative reference"))
+ ((string-match "\\`$[<>]" old-lhs)
+ (let ((new (org-table-formula-handle-first/last-rc
+ old-lhs)))
+ (when (assoc new eqlist)
+ (user-error "\"%s=\" formula tries to overwrite \
+existing formula for column %s"
+ old-lhs
+ new))
+ new))
+ (t old-lhs)))))
+ (if (string-match-p "\\`\\$[0-9]+\\'" lhs)
+ (push (cons lhs rhs) eqlcol)
+ (push (cons lhs rhs) eqlfield))))
+ (setq eqlcol (nreverse eqlcol))
+ ;; Expand ranges in lhs of formulas
+ (setq eqlfield (org-table-expand-lhs-ranges (nreverse eqlfield)))
+ ;; Get the correct line range to process.
+ (if all
+ (progn
+ (setq end (copy-marker (org-table-end)))
+ (goto-char (setq beg org-table-current-begin-pos))
+ (cond
+ ((re-search-forward org-table-calculate-mark-regexp end t)
+ ;; This is a table with marked lines, compute selected
+ ;; lines.
+ (setq line-re org-table-recalculate-regexp))
+ ;; Move forward to the first non-header line.
+ ((and (re-search-forward org-table-dataline-regexp end t)
+ (re-search-forward org-table-hline-regexp end t)
+ (re-search-forward org-table-dataline-regexp end t))
+ (setq beg (match-beginning 0)))
+ ;; Just leave BEG at the start of the table.
+ (t nil)))
+ (setq beg (line-beginning-position)
+ end (copy-marker (line-beginning-position 2))))
+ (goto-char beg)
+ ;; Mark named fields untouchable. Also check if several
+ ;; field/range formulas try to set the same field.
+ (remove-text-properties beg end '(org-untouchable t))
+ (let ((current-line (count-lines org-table-current-begin-pos
+ (line-beginning-position)))
+ seen-fields)
+ (dolist (eq eqlfield)
+ (let* ((name (car eq))
+ (location (assoc name org-table-named-field-locations))
+ (eq-line (or (nth 1 location)
+ (and (string-match "\\`@\\([0-9]+\\)" name)
+ (aref org-table-dlines
+ (string-to-number
+ (match-string 1 name))))))
+ (reference
+ (if location
+ ;; Turn field coordinates associated to NAME
+ ;; into an absolute reference.
+ (format "@%d$%d"
+ (org-table-line-to-dline eq-line)
+ (nth 2 location))
+ name)))
+ (when (member reference seen-fields)
+ (user-error "Several field/range formulas try to set %s"
+ reference))
+ (push reference seen-fields)
+ (when (or all (eq eq-line current-line))
+ (org-table-goto-field name)
+ (org-table-put-field-property :org-untouchable t)))))
+ ;; Evaluate the column formulas, but skip fields covered by
+ ;; field formulas.
+ (goto-char beg)
+ (while (re-search-forward line-re end t)
+ (unless (string-match "\\` *[_^!$/] *\\'" (org-table-get-field 1))
+ ;; Unprotected line, recalculate.
+ (cl-incf cnt)
+ (when all
+ (setq log-last-time
+ (org-table-message-once-per-second
+ log-last-time
+ "Re-applying formulas to full table...(line %d)" cnt)))
+ (if (markerp org-last-recalc-line)
+ (move-marker org-last-recalc-line (line-beginning-position))
+ (setq org-last-recalc-line
+ (copy-marker (line-beginning-position))))
+ (dolist (entry eqlcol)
+ (goto-char org-last-recalc-line)
+ (org-table-goto-column
+ (string-to-number (substring (car entry) 1)) nil 'force)
+ (unless (get-text-property (point) :org-untouchable)
+ (org-table-eval-formula
+ nil (cdr entry) 'noalign 'nocst 'nostore 'noanalysis)))))
+ ;; Evaluate the field formulas.
+ (dolist (eq eqlfield)
+ (let ((reference (car eq))
+ (formula (cdr eq)))
+ (setq log-last-time
+ (org-table-message-once-per-second
+ (and all log-last-time)
+ "Re-applying formula to field: %s" (car eq)))
+ (org-table-goto-field
+ reference
+ ;; Possibly create a new column, as long as
+ ;; `org-table-formula-create-columns' allows it.
+ (let ((column-count (progn (end-of-line)
+ (1- (org-table-current-column)))))
+ (lambda (column)
+ (when (> column 1000)
+ (user-error "Formula column target too large"))
+ (and (> column column-count)
+ (or (eq org-table-formula-create-columns t)
+ (and (eq org-table-formula-create-columns 'warn)
+ (progn
+ (org-display-warning
+ "Out-of-bounds formula added columns")
+ t))
+ (and (eq org-table-formula-create-columns 'prompt)
+ (yes-or-no-p
+ "Out-of-bounds formula. Add columns? ")))))))
+ (org-table-eval-formula nil formula t t t t))))
+ ;; Clean up markers and internal text property.
+ (remove-text-properties (point-min) (point-max) '(org-untouchable t))
+ (set-marker end nil)
+ (unless noalign
+ (when org-table-may-need-update (org-table-align))
+ (when all
+ (org-table-message-once-per-second
+ log-first-time "Re-applying formulas to %d lines... done" cnt)))
+ (org-table-message-once-per-second
+ (and all log-first-time) "Re-applying formulas... done")))))
;;;###autoload
(defun org-table-iterate (&optional arg)
@@ -3145,10 +3329,15 @@ with the prefix ARG."
(defun org-table-recalculate-buffer-tables ()
"Recalculate all tables in the current buffer."
(interactive)
- (save-excursion
- (save-restriction
- (widen)
- (org-table-map-tables (lambda () (org-table-recalculate t)) t))))
+ (org-with-wide-buffer
+ (org-table-map-tables
+ (lambda ()
+ ;; Reason for separate `org-table-align': When repeating
+ ;; (org-table-recalculate t) `org-table-may-need-update' gets in
+ ;; the way.
+ (org-table-recalculate t t)
+ (org-table-align))
+ t)))
;;;###autoload
(defun org-table-iterate-buffer-tables ()
@@ -3158,85 +3347,90 @@ with the prefix ARG."
(i imax)
(checksum (md5 (buffer-string)))
c1)
- (save-excursion
- (save-restriction
- (widen)
- (catch 'exit
- (while (> i 0)
- (setq i (1- i))
- (org-table-map-tables (lambda () (org-table-recalculate t)) t)
- (if (equal checksum (setq c1 (md5 (buffer-string))))
- (progn
- (message "Convergence after %d iterations" (- imax i))
- (throw 'exit t))
- (setq checksum c1)))
- (user-error "No convergence after %d iterations" imax))))))
+ (org-with-wide-buffer
+ (catch 'exit
+ (while (> i 0)
+ (setq i (1- i))
+ (org-table-map-tables (lambda () (org-table-recalculate t t)) t)
+ (if (equal checksum (setq c1 (md5 (buffer-string))))
+ (progn
+ (org-table-map-tables #'org-table-align t)
+ (message "Convergence after %d iterations" (- imax i))
+ (throw 'exit t))
+ (setq checksum c1)))
+ (org-table-map-tables #'org-table-align t)
+ (user-error "No convergence after %d iterations" imax)))))
(defun org-table-calc-current-TBLFM (&optional arg)
"Apply the #+TBLFM in the line at point to the table."
(interactive "P")
(unless (org-at-TBLFM-p) (user-error "Not at a #+TBLFM line"))
(let ((formula (buffer-substring
- (point-at-bol)
- (point-at-eol)))
- s e)
+ (line-beginning-position)
+ (line-end-position))))
(save-excursion
;; Insert a temporary formula at right after the table
(goto-char (org-table-TBLFM-begin))
- (setq s (point-marker))
- (insert (concat formula "\n"))
- (setq e (point-marker))
- ;; Recalculate the table
- (beginning-of-line 0) ; move to the inserted line
- (skip-chars-backward " \r\n\t")
- (if (org-at-table-p)
+ (let ((s (point-marker)))
+ (insert formula "\n")
+ (let ((e (point-marker)))
+ ;; Recalculate the table.
+ (beginning-of-line 0) ; move to the inserted line
+ (skip-chars-backward " \r\n\t")
(unwind-protect
- (org-call-with-arg 'org-table-recalculate (or arg t))
- ;; delete the formula inserted temporarily
- (delete-region s e))))))
+ (org-call-with-arg #'org-table-recalculate (or arg t))
+ ;; Delete the formula inserted temporarily.
+ (delete-region s e)
+ (set-marker s nil)
+ (set-marker e nil)))))))
(defun org-table-TBLFM-begin ()
"Find the beginning of the TBLFM lines and return its position.
Return nil when the beginning of TBLFM line was not found."
(save-excursion
(when (progn (forward-line 1)
- (re-search-backward
- org-table-TBLFM-begin-regexp
- nil t))
- (point-at-bol 2))))
+ (re-search-backward org-table-TBLFM-begin-regexp nil t))
+ (line-beginning-position 2))))
(defun org-table-expand-lhs-ranges (equations)
"Expand list of formulas.
-If some of the RHS in the formulas are ranges or a row reference, expand
-them to individual field equations for each field."
- (let (e res lhs rhs range r1 r2 c1 c2)
- (while (setq e (pop equations))
- (setq lhs (car e) rhs (cdr e))
- (cond
- ((string-match "^@-?[-+0-9]+\\$-?[0-9]+$" lhs)
- ;; This just refers to one fixed field
- (push e res))
- ((string-match "^[a-zA-Z][_a-zA-Z0-9]*$" lhs)
- ;; This just refers to one fixed named field
- (push e res))
- ((string-match "^@[0-9]+$" lhs)
- (loop for ic from 1 to org-table-current-ncol do
- (push (cons (format "%s$%d" lhs ic) rhs) res)
- (put-text-property 0 (length (caar res))
- :orig-eqn e (caar res))))
- (t
- (setq range (org-table-get-range lhs org-table-current-begin-pos
- 1 nil 'corners))
- (setq r1 (nth 0 range) c1 (nth 1 range)
- r2 (nth 2 range) c2 (nth 3 range))
- (setq r1 (org-table-line-to-dline r1))
- (setq r2 (org-table-line-to-dline r2 'above))
- (loop for ir from r1 to r2 do
- (loop for ic from c1 to c2 do
- (push (cons (format "@%d$%d" ir ic) rhs) res)
- (put-text-property 0 (length (caar res))
- :orig-eqn e (caar res)))))))
- (nreverse res)))
+If some of the RHS in the formulas are ranges or a row reference,
+expand them to individual field equations for each field. This
+function assumes the table is already analyzed (i.e., using
+`org-table-analyze')."
+ (let (res)
+ (dolist (e equations (nreverse res))
+ (let ((lhs (car e))
+ (rhs (cdr e)))
+ (cond
+ ((string-match-p "\\`@-?[-+0-9]+\\$-?[0-9]+\\'" lhs)
+ ;; This just refers to one fixed field.
+ (push e res))
+ ((string-match-p "\\`[a-zA-Z][_a-zA-Z0-9]*\\'" lhs)
+ ;; This just refers to one fixed named field.
+ (push e res))
+ ((string-match-p "\\`\\$[0-9]+\\'" lhs)
+ ;; Column formulas are treated specially and are not
+ ;; expanded.
+ (push e res))
+ ((string-match "\\`@[0-9]+\\'" lhs)
+ (dotimes (ic org-table-current-ncol)
+ (push (cons (propertize (format "%s$%d" lhs (1+ ic)) :orig-eqn e)
+ rhs)
+ res)))
+ (t
+ (let* ((range (org-table-get-range
+ lhs org-table-current-begin-pos 1 nil 'corners))
+ (r1 (org-table-line-to-dline (nth 0 range)))
+ (c1 (nth 1 range))
+ (r2 (org-table-line-to-dline (nth 2 range) 'above))
+ (c2 (nth 3 range)))
+ (cl-loop for ir from r1 to r2 do
+ (cl-loop for ic from c1 to c2 do
+ (push (cons (propertize
+ (format "@%d$%d" ir ic) :orig-eqn e)
+ rhs)
+ res))))))))))
(defun org-table-formula-handle-first/last-rc (s)
"Replace @<, @>, $<, $> with first/last row/column of the table.
@@ -3262,32 +3456,40 @@ borders of the table using the @< @> $< $> makers."
(- nmax len -1)))
(if (or (< n 1) (> n nmax))
(user-error "Reference \"%s\" in expression \"%s\" points outside table"
- (match-string 0 s) s))
+ (match-string 0 s) s))
(setq start (match-beginning 0))
(setq s (replace-match (format "%s%d" (match-string 1 s) n) t t s)))))
s)
(defun org-table-formula-substitute-names (f)
"Replace $const with values in string F."
- (let ((start 0) a (f1 f) (pp (/= (string-to-char f) ?')))
- ;; First, check for column names
- (while (setq start (string-match org-table-column-name-regexp f start))
- (setq start (1+ start))
- (setq a (assoc (match-string 1 f) org-table-column-names))
- (setq f (replace-match (concat "$" (cdr a)) t t f)))
- ;; Parameters and constants
- (setq start 0)
- (while (setq start (string-match "\\$\\([a-zA-Z][_a-zA-Z0-9]*\\)\\|\\(\\]" (car entry)) 'column)
- ((equal (string-to-char (car entry)) ?@) 'field)
- ((string-match "^[0-9]" (car entry)) 'column)
- (t 'named)))
- (when (setq title (assq type titles))
- (or (bobp) (insert "\n"))
- (insert (org-add-props (cdr title) nil 'face font-lock-comment-face))
- (setq titles (remove title titles)))
- (if (equal key (car entry)) (setq startline (org-current-line)))
- (setq s (concat (if (member (string-to-char (car entry)) '(?@ ?$)) "" "$")
- (car entry) " = " (cdr entry) "\n"))
- (remove-text-properties 0 (length s) '(face nil) s)
- (insert s))
- (if (eq org-table-use-standard-references t)
+ (let ((at-tblfm (org-at-TBLFM-p)))
+ (unless (or at-tblfm (org-at-table-p))
+ (user-error "Not at a table"))
+ (save-excursion
+ ;; Move point within the table before analyzing it.
+ (when at-tblfm (re-search-backward "^[ \t]*|"))
+ (org-table-analyze))
+ (let ((key (org-table-current-field-formula 'key 'noerror))
+ (eql (sort (org-table-get-stored-formulas t (and at-tblfm (point)))
+ #'org-table-formula-less-p))
+ (pos (point-marker))
+ (source (copy-marker (line-beginning-position)))
+ (startline 1)
+ (wc (current-window-configuration))
+ (sel-win (selected-window))
+ (titles '((column . "# Column Formulas\n")
+ (field . "# Field and Range Formulas\n")
+ (named . "# Named Field Formulas\n"))))
+ (org-switch-to-buffer-other-window "*Edit Formulas*")
+ (erase-buffer)
+ ;; Keep global-font-lock-mode from turning on font-lock-mode
+ (let ((font-lock-global-modes '(not fundamental-mode)))
+ (fundamental-mode))
+ (setq-local font-lock-global-modes (list 'not major-mode))
+ (setq-local org-pos pos)
+ (setq-local org-table--fedit-source source)
+ (setq-local org-window-configuration wc)
+ (setq-local org-selected-window sel-win)
+ (use-local-map org-table-fedit-map)
+ (add-hook 'post-command-hook #'org-table-fedit-post-command t t)
+ (easy-menu-add org-table-fedit-menu)
+ (setq startline (org-current-line))
+ (dolist (entry eql)
+ (let* ((type (cond
+ ((string-match "\\`$\\([0-9]+\\|[<>]+\\)\\'" (car entry))
+ 'column)
+ ((equal (string-to-char (car entry)) ?@) 'field)
+ (t 'named)))
+ (title (assq type titles)))
+ (when title
+ (unless (bobp) (insert "\n"))
+ (insert
+ (org-add-props (cdr title) nil 'face font-lock-comment-face))
+ (setq titles (remove title titles)))
+ (when (equal key (car entry)) (setq startline (org-current-line)))
+ (let ((s (concat
+ (if (memq (string-to-char (car entry)) '(?@ ?$)) "" "$")
+ (car entry) " = " (cdr entry) "\n")))
+ (remove-text-properties 0 (length s) '(face nil) s)
+ (insert s))))
+ (when (eq org-table-use-standard-references t)
(org-table-fedit-toggle-ref-type))
- (org-goto-line startline)
- (message "%s" "Edit formulas, finish with C-c C-c or C-c '. See menu for more commands.")))
+ (org-goto-line startline)
+ (message "%s" (substitute-command-keys "\\\
+Edit formulas, finish with `\\[org-ctrl-c-ctrl-c]' or `\\[org-edit-special]'. \
+See menu for more commands.")))))
(defun org-table-fedit-post-command ()
(when (not (memq this-command '(lisp-complete-symbol)))
(let ((win (selected-window)))
(save-excursion
- (condition-case nil
- (org-table-show-reference)
- (error nil))
+ (ignore-errors (org-table-show-reference))
(select-window win)))))
(defun org-table-formula-to-user (s)
@@ -3537,23 +3748,24 @@ minutes or seconds."
(format "%.1f" (/ (float secs0) 60)))
((eq output-format 'seconds)
(format "%d" secs0))
- (t (org-format-seconds "%.2h:%.2m:%.2s" secs0)))))
+ (t (format-seconds "%.2h:%.2m:%.2s" secs0)))))
(if (< secs 0) (concat "-" res) res)))
(defun org-table-fedit-convert-buffer (function)
"Convert all references in this buffer, using FUNCTION."
- (let ((line (org-current-line)))
+ (let ((origin (copy-marker (line-beginning-position))))
(goto-char (point-min))
(while (not (eobp))
- (insert (funcall function (buffer-substring (point) (point-at-eol))))
- (delete-region (point) (point-at-eol))
- (or (eobp) (forward-char 1)))
- (org-goto-line line)))
+ (insert (funcall function (buffer-substring (point) (line-end-position))))
+ (delete-region (point) (line-end-position))
+ (forward-line))
+ (goto-char origin)
+ (set-marker origin nil)))
(defun org-table-fedit-toggle-ref-type ()
"Convert all references in the buffer from B3 to @3$2 and back."
(interactive)
- (org-set-local 'org-table-buffer-is-an (not org-table-buffer-is-an))
+ (setq-local org-table-buffer-is-an (not org-table-buffer-is-an))
(org-table-fedit-convert-buffer
(if org-table-buffer-is-an
'org-table-convert-refs-to-an 'org-table-convert-refs-to-rc))
@@ -3579,16 +3791,16 @@ minutes or seconds."
(defun org-table-fedit-shift-reference (dir)
(cond
- ((org-at-regexp-p "\\(\\<[a-zA-Z]\\)&")
+ ((org-in-regexp "\\(\\<[a-zA-Z]\\)&")
(if (memq dir '(left right))
(org-rematch-and-replace 1 (eq dir 'left))
(user-error "Cannot shift reference in this direction")))
- ((org-at-regexp-p "\\(\\<[a-zA-Z]\\{1,2\\}\\)\\([0-9]+\\)")
+ ((org-in-regexp "\\(\\<[a-zA-Z]\\{1,2\\}\\)\\([0-9]+\\)")
;; A B3-like reference
(if (memq dir '(up down))
(org-rematch-and-replace 2 (eq dir 'up))
(org-rematch-and-replace 1 (eq dir 'left))))
- ((org-at-regexp-p
+ ((org-in-regexp
"\\(@\\|\\.\\.\\)\\([-+]?\\(I+\\>\\|[0-9]+\\)\\)\\(\\$\\([-+]?[0-9]+\\)\\)?")
;; An internal reference
(if (memq dir '(up down))
@@ -3649,32 +3861,31 @@ a translation reference."
With prefix ARG, apply the new formulas to the table."
(interactive "P")
(org-table-remove-rectangle-highlight)
- (if org-table-use-standard-references
- (progn
- (org-table-fedit-convert-buffer 'org-table-convert-refs-to-rc)
- (setq org-table-buffer-is-an nil)))
- (let ((pos org-pos) (sel-win org-selected-window) eql var form)
+ (when org-table-use-standard-references
+ (org-table-fedit-convert-buffer 'org-table-convert-refs-to-rc)
+ (setq org-table-buffer-is-an nil))
+ (let ((pos org-pos)
+ (sel-win org-selected-window)
+ (source org-table--fedit-source)
+ eql)
(goto-char (point-min))
(while (re-search-forward
"^\\(@[-+I<>0-9.$@]+\\|@?[0-9]+\\|\\$\\([a-zA-Z0-9]+\\|[<>]+\\)\\) *= *\\(.*\\(\n[ \t]+.*$\\)*\\)"
nil t)
- (setq var (if (match-end 2) (match-string 2) (match-string 1))
- form (match-string 3))
- (setq form (org-trim form))
- (when (not (equal form ""))
- (while (string-match "[ \t]*\n[ \t]*" form)
- (setq form (replace-match " " t t form)))
- (when (assoc var eql)
- (user-error "Double formulas for %s" var))
- (push (cons var form) eql)))
- (setq org-pos nil)
+ (let ((var (match-string 1))
+ (form (org-trim (match-string 3))))
+ (unless (equal form "")
+ (while (string-match "[ \t]*\n[ \t]*" form)
+ (setq form (replace-match " " t t form)))
+ (when (assoc var eql)
+ (user-error "Double formulas for %s" var))
+ (push (cons var form) eql))))
(set-window-configuration org-window-configuration)
(select-window sel-win)
- (goto-char pos)
- (unless (org-at-table-p)
- (user-error "Lost table position - cannot install formulas"))
+ (goto-char source)
(org-table-store-formulas eql)
- (move-marker pos nil)
+ (set-marker pos nil)
+ (set-marker source nil)
(kill-buffer "*Edit Formulas*")
(if arg
(org-table-recalculate 'all)
@@ -3733,9 +3944,11 @@ With prefix ARG, apply the new formulas to the table."
(defvar org-show-positions nil)
(defun org-table-show-reference (&optional local)
- "Show the location/value of the $ expression at point."
+ "Show the location/value of the $ expression at point.
+When LOCAL is non-nil, show references for the table at point."
(interactive)
(org-table-remove-rectangle-highlight)
+ (when local (org-table-analyze))
(catch 'exit
(let ((pos (if local (point) org-pos))
(face2 'highlight)
@@ -3743,41 +3956,41 @@ With prefix ARG, apply the new formulas to the table."
(win (selected-window))
(org-show-positions nil)
var name e what match dest)
- (if local (org-table-get-specials))
(setq what (cond
- ((org-at-regexp-p "^@[0-9]+[ \t=]")
+ ((org-in-regexp "^@[0-9]+[ \t=]")
(setq match (concat (substring (match-string 0) 0 -1)
"$1.."
(substring (match-string 0) 0 -1)
"$100"))
'range)
- ((or (org-at-regexp-p org-table-range-regexp2)
- (org-at-regexp-p org-table-translate-regexp)
- (org-at-regexp-p org-table-range-regexp))
+ ((or (org-in-regexp org-table-range-regexp2)
+ (org-in-regexp org-table-translate-regexp)
+ (org-in-regexp org-table-range-regexp))
(setq match
(save-match-data
(org-table-convert-refs-to-rc (match-string 0))))
'range)
- ((org-at-regexp-p "\\$[a-zA-Z][a-zA-Z0-9]*") 'name)
- ((org-at-regexp-p "\\$[0-9]+") 'column)
+ ((org-in-regexp "\\$[a-zA-Z][a-zA-Z0-9]*") 'name)
+ ((org-in-regexp "\\$[0-9]+") 'column)
((not local) nil)
(t (user-error "No reference at point")))
match (and what (or match (match-string 0))))
(when (and match (not (equal (match-beginning 0) (point-at-bol))))
(org-table-add-rectangle-overlay (match-beginning 0) (match-end 0)
'secondary-selection))
- (org-add-hook 'before-change-functions
- 'org-table-remove-rectangle-highlight)
- (if (eq what 'name) (setq var (substring match 1)))
+ (add-hook 'before-change-functions
+ #'org-table-remove-rectangle-highlight)
+ (when (eq what 'name) (setq var (substring match 1)))
(when (eq what 'range)
- (or (equal (string-to-char match) ?@) (setq match (concat "@" match)))
+ (unless (eq (string-to-char match) ?@) (setq match (concat "@" match)))
(setq match (org-table-formula-substitute-names match)))
(unless local
(save-excursion
- (end-of-line 1)
+ (end-of-line)
(re-search-backward "^\\S-" nil t)
- (beginning-of-line 1)
- (when (looking-at "\\(\\$[0-9a-zA-Z]+\\|@[0-9]+\\$[0-9]+\\|[a-zA-Z]+\\([0-9]+\\|&\\)\\) *=")
+ (beginning-of-line)
+ (when (looking-at "\\(\\$[0-9a-zA-Z]+\\|@[0-9]+\\$[0-9]+\\|[a-zA-Z]+\
+\\([0-9]+\\|&\\)\\) *=")
(setq dest
(save-match-data
(org-table-convert-refs-to-rc (match-string 1))))
@@ -3790,60 +4003,52 @@ With prefix ARG, apply the new formulas to the table."
(marker-buffer pos)))))
(goto-char pos)
(org-table-force-dataline)
- (when dest
- (setq name (substring dest 1))
- (cond
- ((string-match "^\\$[a-zA-Z][a-zA-Z0-9]*" dest)
- (setq e (assoc name org-table-named-field-locations))
- (org-goto-line (nth 1 e))
- (org-table-goto-column (nth 2 e)))
- ((string-match "^@\\([0-9]+\\)\\$\\([0-9]+\\)" dest)
- (let ((l (string-to-number (match-string 1 dest)))
- (c (string-to-number (match-string 2 dest))))
- (org-goto-line (aref org-table-dlines l))
- (org-table-goto-column c)))
- (t (org-table-goto-column (string-to-number name))))
- (move-marker pos (point))
- (org-table-highlight-rectangle nil nil face2))
- (cond
- ((equal dest match))
- ((not match))
- ((eq what 'range)
- (condition-case nil
- (save-excursion
- (org-table-get-range match nil nil 'highlight))
- (error nil)))
- ((setq e (assoc var org-table-named-field-locations))
- (org-goto-line (nth 1 e))
- (org-table-goto-column (nth 2 e))
- (org-table-highlight-rectangle (point) (point))
- (message "Named field, column %d of line %d" (nth 2 e) (nth 1 e)))
- ((setq e (assoc var org-table-column-names))
- (org-table-goto-column (string-to-number (cdr e)))
- (org-table-highlight-rectangle (point) (point))
- (goto-char (org-table-begin))
- (if (re-search-forward (concat "^[ \t]*| *! *.*?| *\\(" var "\\) *|")
- (org-table-end) t)
- (progn
- (goto-char (match-beginning 1))
- (org-table-highlight-rectangle)
- (message "Named column (column %s)" (cdr e)))
- (user-error "Column name not found")))
- ((eq what 'column)
- ;; column number
- (org-table-goto-column (string-to-number (substring match 1)))
- (org-table-highlight-rectangle (point) (point))
- (message "Column %s" (substring match 1)))
- ((setq e (assoc var org-table-local-parameters))
- (goto-char (org-table-begin))
- (if (re-search-forward (concat "^[ \t]*| *\\$ *.*?| *\\(" var "=\\)") nil t)
- (progn
- (goto-char (match-beginning 1))
- (org-table-highlight-rectangle)
- (message "Local parameter."))
- (user-error "Parameter not found")))
- (t
+ (let ((table-start
+ (if local org-table-current-begin-pos (org-table-begin))))
+ (when dest
+ (setq name (substring dest 1))
+ (cond
+ ((string-match-p "\\`\\$[a-zA-Z][a-zA-Z0-9]*" dest)
+ (org-table-goto-field dest))
+ ((string-match-p "\\`@\\([1-9][0-9]*\\)\\$\\([1-9][0-9]*\\)\\'"
+ dest)
+ (org-table-goto-field dest))
+ (t (org-table-goto-column (string-to-number name))))
+ (move-marker pos (point))
+ (org-table-highlight-rectangle nil nil face2))
(cond
+ ((equal dest match))
+ ((not match))
+ ((eq what 'range)
+ (ignore-errors (org-table-get-range match table-start nil 'highlight)))
+ ((setq e (assoc var org-table-named-field-locations))
+ (org-table-goto-field var)
+ (org-table-highlight-rectangle)
+ (message "Named field, column %d of line %d" (nth 2 e) (nth 1 e)))
+ ((setq e (assoc var org-table-column-names))
+ (org-table-goto-column (string-to-number (cdr e)))
+ (org-table-highlight-rectangle)
+ (goto-char table-start)
+ (if (re-search-forward (concat "^[ \t]*| *! *.*?| *\\(" var "\\) *|")
+ (org-table-end) t)
+ (progn
+ (goto-char (match-beginning 1))
+ (org-table-highlight-rectangle)
+ (message "Named column (column %s)" (cdr e)))
+ (user-error "Column name not found")))
+ ((eq what 'column)
+ ;; Column number.
+ (org-table-goto-column (string-to-number (substring match 1)))
+ (org-table-highlight-rectangle)
+ (message "Column %s" (substring match 1)))
+ ((setq e (assoc var org-table-local-parameters))
+ (goto-char table-start)
+ (if (re-search-forward (concat "^[ \t]*| *\\$ *.*?| *\\(" var "=\\)") nil t)
+ (progn
+ (goto-char (match-beginning 1))
+ (org-table-highlight-rectangle)
+ (message "Local parameter."))
+ (user-error "Parameter not found")))
((not var) (user-error "No reference at point"))
((setq e (assoc var org-table-formula-constants-local))
(message "Local Constant: $%s=%s in #+CONSTANTS line."
@@ -3854,19 +4059,19 @@ With prefix ARG, apply the new formulas to the table."
((setq e (and (fboundp 'constants-get) (constants-get var)))
(message "Constant: $%s=%s, from `constants.el'%s."
var e (format " (%s units)" constants-unit-system)))
- (t (user-error "Undefined name $%s" var)))))
- (goto-char pos)
- (when (and org-show-positions
- (not (memq this-command '(org-table-fedit-scroll
- org-table-fedit-scroll-down))))
- (push pos org-show-positions)
- (push org-table-current-begin-pos org-show-positions)
- (let ((min (apply 'min org-show-positions))
- (max (apply 'max org-show-positions)))
- (set-window-start (selected-window) min)
- (goto-char max)
- (or (pos-visible-in-window-p max)
- (set-window-start (selected-window) max))))
+ (t (user-error "Undefined name $%s" var)))
+ (goto-char pos)
+ (when (and org-show-positions
+ (not (memq this-command '(org-table-fedit-scroll
+ org-table-fedit-scroll-down))))
+ (push pos org-show-positions)
+ (push table-start org-show-positions)
+ (let ((min (apply 'min org-show-positions))
+ (max (apply 'max org-show-positions)))
+ (set-window-start (selected-window) min)
+ (goto-char max)
+ (or (pos-visible-in-window-p max)
+ (set-window-start (selected-window) max)))))
(select-window win))))
(defun org-table-force-dataline ()
@@ -3926,43 +4131,49 @@ Use COMMAND to do the motion, repeat if necessary to end up in a data line."
(push ov org-table-rectangle-overlays)))
(defun org-table-highlight-rectangle (&optional beg end face)
- "Highlight rectangular region in a table."
- (setq beg (or beg (point)) end (or end (point)))
- (let ((b (min beg end))
- (e (max beg end))
- l1 c1 l2 c2 tmp)
- (and (boundp 'org-show-positions)
- (setq org-show-positions (cons b (cons e org-show-positions))))
- (goto-char (min beg end))
- (setq l1 (org-current-line)
- c1 (org-table-current-column))
- (goto-char (max beg end))
- (setq l2 (org-current-line)
- c2 (org-table-current-column))
- (if (> c1 c2) (setq tmp c1 c1 c2 c2 tmp))
- (org-goto-line l1)
- (beginning-of-line 1)
- (loop for line from l1 to l2 do
- (when (looking-at org-table-dataline-regexp)
- (org-table-goto-column c1)
- (skip-chars-backward "^|\n") (setq beg (point))
- (org-table-goto-column c2)
- (skip-chars-forward "^|\n") (setq end (point))
- (org-table-add-rectangle-overlay beg end face))
- (beginning-of-line 2))
- (goto-char b))
- (add-hook 'before-change-functions 'org-table-remove-rectangle-highlight))
-
-(defun org-table-remove-rectangle-highlight (&rest ignore)
+ "Highlight rectangular region in a table.
+When buffer positions BEG and END are provided, use them to
+delimit the region to highlight. Otherwise, refer to point. Use
+FACE, when non-nil, for the highlight."
+ (let* ((beg (or beg (point)))
+ (end (or end (point)))
+ (b (min beg end))
+ (e (max beg end))
+ (start-coordinates
+ (save-excursion
+ (goto-char b)
+ (cons (line-beginning-position) (org-table-current-column))))
+ (end-coordinates
+ (save-excursion
+ (goto-char e)
+ (cons (line-beginning-position) (org-table-current-column)))))
+ (when (boundp 'org-show-positions)
+ (setq org-show-positions (cons b (cons e org-show-positions))))
+ (goto-char (car start-coordinates))
+ (let ((column-start (min (cdr start-coordinates) (cdr end-coordinates)))
+ (column-end (max (cdr start-coordinates) (cdr end-coordinates)))
+ (last-row (car end-coordinates)))
+ (while (<= (point) last-row)
+ (when (looking-at org-table-dataline-regexp)
+ (org-table-goto-column column-start)
+ (skip-chars-backward "^|\n")
+ (let ((p (point)))
+ (org-table-goto-column column-end)
+ (skip-chars-forward "^|\n")
+ (org-table-add-rectangle-overlay p (point) face)))
+ (forward-line)))
+ (goto-char (car start-coordinates)))
+ (add-hook 'before-change-functions #'org-table-remove-rectangle-highlight))
+
+(defun org-table-remove-rectangle-highlight (&rest _ignore)
"Remove the rectangle overlays."
(unless org-inhibit-highlight-removal
(remove-hook 'before-change-functions 'org-table-remove-rectangle-highlight)
(mapc 'delete-overlay org-table-rectangle-overlays)
(setq org-table-rectangle-overlays nil)))
-(defvar org-table-coordinate-overlays nil
+(defvar-local org-table-coordinate-overlays nil
"Collects the coordinate grid overlays, so that they can be removed.")
-(make-variable-buffer-local 'org-table-coordinate-overlays)
(defun org-table-overlay-coordinates ()
"Add overlays to the table at point, to show row/column coordinates."
@@ -4017,19 +4228,20 @@ Use COMMAND to do the motion, repeat if necessary to end up in a data line."
;;; The orgtbl minor mode
;; Define a minor mode which can be used in other modes in order to
-;; integrate the org-mode table editor.
-
-;; This is really a hack, because the org-mode table editor uses several
-;; keys which normally belong to the major mode, for example the TAB and
-;; RET keys. Here is how it works: The minor mode defines all the keys
-;; necessary to operate the table editor, but wraps the commands into a
-;; function which tests if the cursor is currently inside a table. If that
-;; is the case, the table editor command is executed. However, when any of
-;; those keys is used outside a table, the function uses `key-binding' to
-;; look up if the key has an associated command in another currently active
-;; keymap (minor modes, major mode, global), and executes that command.
-;; There might be problems if any of the keys used by the table editor is
-;; otherwise used as a prefix key.
+;; integrate the Org table editor.
+
+;; This is really a hack, because the Org table editor uses several
+;; keys which normally belong to the major mode, for example the TAB
+;; and RET keys. Here is how it works: The minor mode defines all the
+;; keys necessary to operate the table editor, but wraps the commands
+;; into a function which tests if the cursor is currently inside
+;; a table. If that is the case, the table editor command is
+;; executed. However, when any of those keys is used outside a table,
+;; the function uses `key-binding' to look up if the key has an
+;; associated command in another currently active keymap (minor modes,
+;; major mode, global), and executes that command. There might be
+;; problems if any of the keys used by the table editor is otherwise
+;; used as a prefix key.
;; Another challenge is that the key binding for TAB can be tab or \C-i,
;; likewise the binding for RET can be return or \C-m. Orgtbl-mode
@@ -4079,16 +4291,16 @@ Use COMMAND to do the motion, repeat if necessary to end up in a data line."
;; FIXME: maybe it should use emulation-mode-map-alists?
(and c (setq minor-mode-map-alist
(cons c (delq c minor-mode-map-alist)))))
- (org-set-local (quote org-table-may-need-update) t)
- (org-add-hook 'before-change-functions 'org-before-change-function
- nil 'local)
- (org-set-local 'org-old-auto-fill-inhibit-regexp
- auto-fill-inhibit-regexp)
- (org-set-local 'auto-fill-inhibit-regexp
- (if auto-fill-inhibit-regexp
- (concat orgtbl-line-start-regexp "\\|"
- auto-fill-inhibit-regexp)
- orgtbl-line-start-regexp))
+ (setq-local org-table-may-need-update t)
+ (add-hook 'before-change-functions 'org-before-change-function
+ nil 'local)
+ (setq-local org-old-auto-fill-inhibit-regexp
+ auto-fill-inhibit-regexp)
+ (setq-local auto-fill-inhibit-regexp
+ (if auto-fill-inhibit-regexp
+ (concat orgtbl-line-start-regexp "\\|"
+ auto-fill-inhibit-regexp)
+ orgtbl-line-start-regexp))
(add-to-invisibility-spec '(org-cwidth))
(when (fboundp 'font-lock-add-keywords)
(font-lock-add-keywords nil orgtbl-extra-font-lock-keywords)
@@ -4188,27 +4400,26 @@ to execute outside of tables."
cmd (orgtbl-make-binding fun nfunc key))
(org-defkey orgtbl-mode-map key cmd))
- ;; Special treatment needed for TAB and RET
+ ;; Special treatment needed for TAB, RET and DEL
(org-defkey orgtbl-mode-map [(return)]
(orgtbl-make-binding 'orgtbl-ret 100 [(return)] "\C-m"))
(org-defkey orgtbl-mode-map "\C-m"
(orgtbl-make-binding 'orgtbl-ret 101 "\C-m" [(return)]))
-
(org-defkey orgtbl-mode-map [(tab)]
(orgtbl-make-binding 'orgtbl-tab 102 [(tab)] "\C-i"))
(org-defkey orgtbl-mode-map "\C-i"
(orgtbl-make-binding 'orgtbl-tab 103 "\C-i" [(tab)]))
-
(org-defkey orgtbl-mode-map [(shift tab)]
(orgtbl-make-binding 'org-table-previous-field 104
[(shift tab)] [(tab)] "\C-i"))
+ (org-defkey orgtbl-mode-map [backspace]
+ (orgtbl-make-binding 'org-delete-backward-char 109
+ [backspace] (kbd "DEL")))
-
- (unless (featurep 'xemacs)
- (org-defkey orgtbl-mode-map [S-iso-lefttab]
- (orgtbl-make-binding 'org-table-previous-field 107
- [S-iso-lefttab] [backtab] [(shift tab)]
- [(tab)] "\C-i")))
+ (org-defkey orgtbl-mode-map [S-iso-lefttab]
+ (orgtbl-make-binding 'org-table-previous-field 107
+ [S-iso-lefttab] [backtab] [(shift tab)]
+ [(tab)] "\C-i"))
(org-defkey orgtbl-mode-map [backtab]
(orgtbl-make-binding 'org-table-previous-field 108
@@ -4269,7 +4480,7 @@ to execute outside of tables."
"--"
("Radio tables"
["Insert table template" orgtbl-insert-radio-table
- (assq major-mode orgtbl-radio-table-templates)]
+ (cl-assoc-if #'derived-mode-p orgtbl-radio-table-templates)]
["Comment/uncomment table" orgtbl-toggle-comment t])
"--"
["Set Column Formula" org-table-eval-formula :active (org-at-table-p) :keys "C-c ="]
@@ -4290,7 +4501,10 @@ to execute outside of tables."
org-table-toggle-coordinate-overlays :active (org-at-table-p)
:keys "C-c }"
:style toggle :selected org-table-overlay-coordinates]
- ))
+ "--"
+ ("Plot"
+ ["Ascii plot" orgtbl-ascii-plot :active (org-at-table-p) :keys "C-c \" a"]
+ ["Gnuplot" org-plot/gnuplot :active (org-at-table-p) :keys "C-c \" g"])))
t))
(defun orgtbl-ctrl-c-ctrl-c (arg)
@@ -4316,7 +4530,6 @@ With prefix arg, also recompute table."
(when (orgtbl-send-table 'maybe)
(run-hooks 'orgtbl-after-send-table-hook)))
((eq action 'recalc)
- (org-table-set-constants)
(save-excursion
(beginning-of-line 1)
(skip-chars-backward " \r\n\t")
@@ -4325,7 +4538,7 @@ With prefix arg, also recompute table."
(t (let (orgtbl-mode)
(call-interactively (key-binding "\C-c\C-c")))))))
-(defun orgtbl-create-or-convert-from-region (arg)
+(defun orgtbl-create-or-convert-from-region (_arg)
"Create table or convert region to table, if no conflicting binding.
This installs the table binding `C-c |', but only if there is no
conflicting binding to this key outside orgtbl-mode."
@@ -4369,11 +4582,9 @@ overwritten, and the table is not marked as requiring realignment."
(org-table-blank-field))
t)
(eq N 1)
- (looking-at "[^|\n]* +|"))
+ (looking-at "[^|\n]* \\( \\)|"))
(let (org-table-may-need-update)
- (goto-char (1- (match-end 0)))
- (org-delete-backward-char 1)
- (goto-char (match-beginning 0))
+ (delete-region (match-beginning 1) (match-end 1))
(self-insert-command N))
(setq org-table-may-need-update t)
(let* (orgtbl-mode
@@ -4398,6 +4609,7 @@ overwritten, and the table is not marked as requiring realignment."
(setq org-self-insert-command-undo-counter
(1+ org-self-insert-command-undo-counter))))))))
+;;;###autoload
(defvar orgtbl-exp-regexp "^\\([-+]?[0-9][0-9.]*\\)[eE]\\([-+]?[0-9]+\\)$"
"Regular expression matching exponentials as produced by calc.")
@@ -4418,23 +4630,24 @@ a radio table."
(beginning-of-line 0)))
rtn)))
-(defun orgtbl-send-replace-tbl (name txt)
- "Find and replace table NAME with TXT."
+(defun orgtbl-send-replace-tbl (name text)
+ "Find and replace table NAME with TEXT."
(save-excursion
(goto-char (point-min))
- (unless (re-search-forward
- (concat "BEGIN +RECEIVE +ORGTBL +" name "\\([ \t]\\|$\\)") nil t)
- (user-error "Don't know where to insert translated table"))
- (goto-char (match-beginning 0))
- (beginning-of-line 2)
- (save-excursion
- (let ((beg (point)))
- (unless (re-search-forward
- (concat "END +RECEIVE +ORGTBL +" name) nil t)
- (user-error "Cannot find end of insertion region"))
- (beginning-of-line 1)
- (delete-region beg (point))))
- (insert txt "\n")))
+ (let* ((location-flag nil)
+ (name (regexp-quote name))
+ (begin-re (format "BEGIN +RECEIVE +ORGTBL +%s\\([ \t]\\|$\\)" name))
+ (end-re (format "END +RECEIVE +ORGTBL +%s\\([ \t]\\|$\\)" name)))
+ (while (re-search-forward begin-re nil t)
+ (unless location-flag (setq location-flag t))
+ (let ((beg (line-beginning-position 2)))
+ (unless (re-search-forward end-re nil t)
+ (user-error "Cannot find end of receiver location at %d" beg))
+ (beginning-of-line)
+ (delete-region beg (point))
+ (insert text "\n")))
+ (unless location-flag
+ (user-error "No valid receiver location found in the buffer")))))
;;;###autoload
(defun org-table-to-lisp (&optional txt)
@@ -4442,76 +4655,43 @@ a radio table."
The structure will be a list. Each item is either the symbol `hline'
for a horizontal separator line, or a list of field values as strings.
The table is taken from the parameter TXT, or from the buffer at point."
- (unless txt
- (unless (org-at-table-p)
- (user-error "No table at point")))
- (let* ((txt (or txt
- (buffer-substring-no-properties (org-table-begin)
- (org-table-end))))
- (lines (org-split-string txt "[ \t]*\n[ \t]*")))
-
- (mapcar
- (lambda (x)
- (if (string-match org-table-hline-regexp x)
- 'hline
- (org-split-string (org-trim x) "\\s-*|\\s-*")))
- lines)))
+ (unless (or txt (org-at-table-p)) (user-error "No table at point"))
+ (let ((txt (or txt
+ (buffer-substring-no-properties (org-table-begin)
+ (org-table-end)))))
+ (mapcar (lambda (x)
+ (if (string-match org-table-hline-regexp x) 'hline
+ (org-split-string (org-trim x) "\\s-*|\\s-*")))
+ (org-split-string txt "[ \t]*\n[ \t]*"))))
(defun orgtbl-send-table (&optional maybe)
- "Send a transformed version of this table to the receiver position.
-With argument MAYBE, fail quietly if no transformation is defined for
-this table."
+ "Send a transformed version of table at point to the receiver position.
+With argument MAYBE, fail quietly if no transformation is defined
+for this table."
(interactive)
(catch 'exit
(unless (org-at-table-p) (user-error "Not at a table"))
;; when non-interactive, we assume align has just happened.
- (when (org-called-interactively-p 'any) (org-table-align))
+ (when (called-interactively-p 'any) (org-table-align))
(let ((dests (orgtbl-gather-send-defs))
- (txt (buffer-substring-no-properties (org-table-begin)
- (org-table-end)))
+ (table (org-table-to-lisp
+ (buffer-substring-no-properties (org-table-begin)
+ (org-table-end))))
(ntbl 0))
- (unless dests (if maybe (throw 'exit nil)
- (user-error "Don't know how to transform this table")))
+ (unless dests
+ (if maybe (throw 'exit nil)
+ (user-error "Don't know how to transform this table")))
(dolist (dest dests)
- (let* ((name (plist-get dest :name))
- (transform (plist-get dest :transform))
- (params (plist-get dest :params))
- (skip (plist-get params :skip))
- (skipcols (plist-get params :skipcols))
- (no-escape (plist-get params :no-escape))
- beg
- (lines (org-table-clean-before-export
- (nthcdr (or skip 0)
- (org-split-string txt "[ \t]*\n[ \t]*"))))
- (i0 (if org-table-clean-did-remove-column 2 1))
- (lines (if no-escape lines
- (mapcar (lambda(l) (replace-regexp-in-string
- "\\([&%#_^]\\)" "\\\\\\1{}" l)) lines)))
- (table (mapcar
- (lambda (x)
- (if (string-match org-table-hline-regexp x)
- 'hline
- (org-remove-by-index
- (org-split-string (org-trim x) "\\s-*|\\s-*")
- skipcols i0)))
- lines))
- (fun (if (= i0 2) 'cdr 'identity))
- (org-table-last-alignment
- (org-remove-by-index (funcall fun org-table-last-alignment)
- skipcols i0))
- (org-table-last-column-widths
- (org-remove-by-index (funcall fun org-table-last-column-widths)
- skipcols i0))
- (txt (if (fboundp transform)
- (funcall transform table params)
- (user-error "No such transformation function %s" transform))))
- (orgtbl-send-replace-tbl name txt))
- (setq ntbl (1+ ntbl)))
+ (let ((name (plist-get dest :name))
+ (transform (plist-get dest :transform))
+ (params (plist-get dest :params)))
+ (unless (fboundp transform)
+ (user-error "No such transformation function %s" transform))
+ (orgtbl-send-replace-tbl name (funcall transform table params)))
+ (cl-incf ntbl))
(message "Table converted and installed at %d receiver location%s"
ntbl (if (> ntbl 1) "s" ""))
- (if (> ntbl 0)
- ntbl
- nil))))
+ (and (> ntbl 0) ntbl))))
(defun org-remove-by-index (list indices &optional i0)
"Remove the elements in LIST with indices in INDICES.
@@ -4549,7 +4729,7 @@ First element has index 0, or I0 if given."
(defun orgtbl-insert-radio-table ()
"Insert a radio table template appropriate for this major mode."
(interactive)
- (let* ((e (assq major-mode orgtbl-radio-table-templates))
+ (let* ((e (cl-assoc-if #'derived-mode-p orgtbl-radio-table-templates))
(txt (nth 1 e))
name pos)
(unless e (user-error "No radio table setup defined for %s" major-mode))
@@ -4561,356 +4741,512 @@ First element has index 0, or I0 if given."
(insert txt)
(goto-char pos)))
-;; Dynamically bound input and output for table formatting.
-(defvar *orgtbl-table* nil
- "Carries the current table through formatting routines.")
-(defvar *orgtbl-rtn* nil
- "Formatting routines push the output lines here.")
-;; Formatting parameters for the current table section.
-(defvar *orgtbl-hline* nil "Text used for horizontal lines.")
-(defvar *orgtbl-sep* nil "Text used as a column separator.")
-(defvar *orgtbl-default-fmt* nil "Default format for each entry.")
-(defvar *orgtbl-fmt* nil "Format for each entry.")
-(defvar *orgtbl-efmt* nil "Format for numbers.")
-(defvar *orgtbl-lfmt* nil "Format for an entire line, overrides fmt.")
-(defvar *orgtbl-llfmt* nil "Specializes lfmt for the last row.")
-(defvar *orgtbl-lstart* nil "Text starting a row.")
-(defvar *orgtbl-llstart* nil "Specializes lstart for the last row.")
-(defvar *orgtbl-lend* nil "Text ending a row.")
-(defvar *orgtbl-llend* nil "Specializes lend for the last row.")
-
-(defsubst orgtbl-get-fmt (fmt i)
- "Retrieve the format from FMT corresponding to the Ith column."
- (if (and (not (functionp fmt)) (consp fmt))
- (plist-get fmt i)
- fmt))
-
-(defsubst orgtbl-apply-fmt (fmt &rest args)
- "Apply format FMT to arguments ARGS.
-When FMT is nil, return the first argument from ARGS."
- (cond ((functionp fmt) (apply fmt args))
- (fmt (apply 'format fmt args))
- (args (car args))
- (t args)))
-
-(defsubst orgtbl-eval-str (str)
- "If STR is a function, evaluate it with no arguments."
- (if (functionp str)
- (funcall str)
- str))
-
-(defun orgtbl-format-line (line)
- "Format LINE as a table row."
- (if (eq line 'hline) (if *orgtbl-hline* (push *orgtbl-hline* *orgtbl-rtn*))
- (let* ((i 0)
- (line
- (mapcar
- (lambda (f)
- (setq i (1+ i))
- (let* ((efmt (orgtbl-get-fmt *orgtbl-efmt* i))
- (f (if (and efmt (string-match orgtbl-exp-regexp f))
- (orgtbl-apply-fmt efmt (match-string 1 f)
- (match-string 2 f))
- f)))
- (orgtbl-apply-fmt (or (orgtbl-get-fmt *orgtbl-fmt* i)
- *orgtbl-default-fmt*)
- f)))
- line)))
- (push (if *orgtbl-lfmt*
- (apply #'orgtbl-apply-fmt *orgtbl-lfmt* line)
- (concat (orgtbl-eval-str *orgtbl-lstart*)
- (mapconcat 'identity line *orgtbl-sep*)
- (orgtbl-eval-str *orgtbl-lend*)))
- *orgtbl-rtn*))))
-
-(defun orgtbl-format-section (section-stopper)
- "Format lines until the first occurrence of SECTION-STOPPER."
- (let (prevline)
- (progn
- (while (not (eq (car *orgtbl-table*) section-stopper))
- (if prevline (orgtbl-format-line prevline))
- (setq prevline (pop *orgtbl-table*)))
- (if prevline (let ((*orgtbl-lstart* *orgtbl-llstart*)
- (*orgtbl-lend* *orgtbl-llend*)
- (*orgtbl-lfmt* *orgtbl-llfmt*))
- (orgtbl-format-line prevline))))))
-
;;;###autoload
-(defun orgtbl-to-generic (table params &optional backend)
+(defun orgtbl-to-generic (table params)
"Convert the orgtbl-mode TABLE to some other format.
+
This generic routine can be used for many standard cases.
-TABLE is a list, each entry either the symbol `hline' for a horizontal
-separator line, or a list of fields for that line.
-PARAMS is a property list of parameters that can influence the conversion.
-A third optional argument BACKEND can be used to convert the content of
-the cells using a specific export back-end.
-For the generic converter, some parameters are obligatory: you need to
-specify either :lfmt, or all of (:lstart :lend :sep).
+TABLE is a list, each entry either the symbol `hline' for
+a horizontal separator line, or a list of fields for that
+line. PARAMS is a property list of parameters that can
+influence the conversion.
Valid parameters are:
-:splice When set to t, return only table body lines, don't wrap
- them into :tstart and :tend. Default is nil. When :splice
- is non-nil, this also means that the exporter should not look
- for and interpret header and footer sections.
+:backend, :raw
+
+ Export back-end used as a basis to transcode elements of the
+ table, when no specific parameter applies to it. It is also
+ used to translate cells contents. You can prevent this by
+ setting :raw property to a non-nil value.
-:hline String to be inserted on horizontal separation lines.
- May be nil to ignore hlines.
+:splice
-:sep Separator between two fields
-:remove-nil-lines Do not include lines that evaluate to nil.
+ When non-nil, only convert rows, not the table itself. This is
+ equivalent to setting to the empty string both :tstart
+ and :tend, which see.
+
+:skip
+
+ When set to an integer N, skip the first N lines of the table.
+ Horizontal separation lines do count for this parameter!
+
+:skipcols
+
+ List of columns that should be skipped. If the table has
+ a column with calculation marks, that column is automatically
+ discarded beforehand.
+
+:hline
+
+ String to be inserted on horizontal separation lines. May be
+ nil to ignore these lines altogether.
+
+:sep
+
+ Separator between two fields, as a string.
Each in the following group may be either a string or a function
of no arguments returning a string:
-:tstart String to start the table. Ignored when :splice is t.
-:tend String to end the table. Ignored when :splice is t.
-:lstart String to start a new table line.
-:llstart String to start the last table line, defaults to :lstart.
-:lend String to end a table line
-:llend String to end the last table line, defaults to :lend.
-
-Each in the following group may be a string, a function of one
-argument (the field or line) returning a string, or a plist
-mapping columns to either of the above:
-
-:lfmt Format for entire line, with enough %s to capture all fields.
- If this is present, :lstart, :lend, and :sep are ignored.
-:llfmt Format for the entire last line, defaults to :lfmt.
-:fmt A format to be used to wrap the field, should contain
- %s for the original field value. For example, to wrap
- everything in dollars, you could use :fmt \"$%s$\".
- This may also be a property list with column numbers and
- formats. For example :fmt (2 \"$%s$\" 4 \"%s%%\")
-:hlstart :hllstart :hlend :hllend :hlsep :hlfmt :hllfmt :hfmt
- Same as above, specific for the header lines in the table.
- All lines before the first hline are treated as header.
- If any of these is not present, the data line value is used.
+:tstart, :tend
+
+ Strings to start and end the table. Ignored when :splice is t.
+
+:lstart, :lend
+
+ Strings to start and end a new table line.
+
+:llstart, :llend
+
+ Strings to start and end the last table line. Default,
+ respectively, to :lstart and :lend.
+
+Each in the following group may be a string or a function of one
+argument (either the cells in the current row, as a list of
+strings, or the current cell) returning a string:
+
+:lfmt
+
+ Format string for an entire row, with enough %s to capture all
+ fields. When non-nil, :lstart, :lend, and :sep are ignored.
+
+:llfmt
+
+ Format for the entire last line, defaults to :lfmt.
+
+:fmt
+
+ A format to be used to wrap the field, should contain %s for
+ the original field value. For example, to wrap everything in
+ dollars, you could use :fmt \"$%s$\". This may also be
+ a property list with column numbers and format strings, or
+ functions, e.g.,
+
+ (:fmt (2 \"$%s$\" 4 (lambda (c) (format \"$%s$\" c))))
+
+:hlstart :hllstart :hlend :hllend :hsep :hlfmt :hllfmt :hfmt
+
+ Same as above, specific for the header lines in the table.
+ All lines before the first hline are treated as header. If
+ any of these is not present, the data line value is used.
This may be either a string or a function of two arguments:
-:efmt Use this format to print numbers with exponentials.
- The format should have %s twice for inserting mantissa
- and exponent, for example \"%s\\\\times10^{%s}\". This
- may also be a property list with column numbers and
- formats. :fmt will still be applied after :efmt.
-
-In addition to this, the parameters :skip and :skipcols are always handled
-directly by `orgtbl-send-table'. See manual."
- (let* ((splicep (plist-get params :splice))
- (hline (plist-get params :hline))
- (skipheadrule (plist-get params :skipheadrule))
- (remove-nil-linesp (plist-get params :remove-nil-lines))
- (remove-newlines (plist-get params :remove-newlines))
- (*orgtbl-hline* hline)
- (*orgtbl-table* table)
- (*orgtbl-sep* (plist-get params :sep))
- (*orgtbl-efmt* (plist-get params :efmt))
- (*orgtbl-lstart* (plist-get params :lstart))
- (*orgtbl-llstart* (or (plist-get params :llstart) *orgtbl-lstart*))
- (*orgtbl-lend* (plist-get params :lend))
- (*orgtbl-llend* (or (plist-get params :llend) *orgtbl-lend*))
- (*orgtbl-lfmt* (plist-get params :lfmt))
- (*orgtbl-llfmt* (or (plist-get params :llfmt) *orgtbl-lfmt*))
- (*orgtbl-fmt* (plist-get params :fmt))
- *orgtbl-rtn*)
- ;; Convert cells content to backend BACKEND
- (when backend
- (setq *orgtbl-table*
- (mapcar
- (lambda(r)
- (if (listp r)
- (mapcar
- (lambda (c)
- (org-trim (org-export-string-as c backend t '(:with-tables t))))
- r)
- r))
- *orgtbl-table*)))
- ;; Put header
- (unless splicep
- (when (plist-member params :tstart)
- (let ((tstart (orgtbl-eval-str (plist-get params :tstart))))
- (if tstart (push tstart *orgtbl-rtn*)))))
- ;; If we have a heading, format it and handle the trailing hline.
- (if (and (not splicep)
- (or (consp (car *orgtbl-table*))
- (consp (nth 1 *orgtbl-table*)))
- (memq 'hline (cdr *orgtbl-table*)))
- (progn
- (when (eq 'hline (car *orgtbl-table*))
- ;; There is a hline before the first data line
- (and hline (push hline *orgtbl-rtn*))
- (pop *orgtbl-table*))
- (let* ((*orgtbl-lstart* (or (plist-get params :hlstart)
- *orgtbl-lstart*))
- (*orgtbl-llstart* (or (plist-get params :hllstart)
- *orgtbl-llstart*))
- (*orgtbl-lend* (or (plist-get params :hlend) *orgtbl-lend*))
- (*orgtbl-llend* (or (plist-get params :hllend)
- (plist-get params :hlend) *orgtbl-llend*))
- (*orgtbl-lfmt* (or (plist-get params :hlfmt) *orgtbl-lfmt*))
- (*orgtbl-llfmt* (or (plist-get params :hllfmt)
- (plist-get params :hlfmt) *orgtbl-llfmt*))
- (*orgtbl-sep* (or (plist-get params :hlsep) *orgtbl-sep*))
- (*orgtbl-fmt* (or (plist-get params :hfmt) *orgtbl-fmt*)))
- (orgtbl-format-section 'hline))
- (if (and hline (not skipheadrule)) (push hline *orgtbl-rtn*))
- (pop *orgtbl-table*)))
- ;; Now format the main section.
- (orgtbl-format-section nil)
- (unless splicep
- (when (plist-member params :tend)
- (let ((tend (orgtbl-eval-str (plist-get params :tend))))
- (if tend (push tend *orgtbl-rtn*)))))
- (mapconcat (if remove-newlines
- (lambda (tend)
- (replace-regexp-in-string "[\n\r\t\f]" "\\\\n" tend))
- 'identity)
- (nreverse (if remove-nil-linesp
- (remq nil *orgtbl-rtn*)
- *orgtbl-rtn*)) "\n")))
+:efmt
+
+ Use this format to print numbers with exponential. The format
+ should have %s twice for inserting mantissa and exponent, for
+ example \"%s\\\\times10^{%s}\". This may also be a property
+ list with column numbers and format strings or functions.
+ :fmt will still be applied after :efmt."
+ ;; Make sure `org-export-create-backend' is available.
+ (require 'ox)
+ (let* ((backend (plist-get params :backend))
+ (custom-backend
+ ;; Build a custom back-end according to PARAMS. Before
+ ;; defining a translator, check if there is anything to do.
+ ;; When there isn't, let BACKEND handle the element.
+ (org-export-create-backend
+ :parent (or backend 'org)
+ :transcoders
+ `((table . ,(org-table--to-generic-table params))
+ (table-row . ,(org-table--to-generic-row params))
+ (table-cell . ,(org-table--to-generic-cell params))
+ ;; Macros are not going to be expanded. However, no
+ ;; regular back-end has a transcoder for them. We
+ ;; provide one so they are not ignored, but displayed
+ ;; as-is instead.
+ (macro . (lambda (m c i) (org-element-macro-interpreter m nil))))))
+ data info)
+ ;; Store TABLE as Org syntax in DATA. Tolerate non-string cells.
+ ;; Initialize communication channel in INFO.
+ (with-temp-buffer
+ (let ((org-inhibit-startup t)) (org-mode))
+ (let ((standard-output (current-buffer)))
+ (dolist (e table)
+ (cond ((eq e 'hline) (princ "|--\n"))
+ ((consp e)
+ (princ "| ") (dolist (c e) (princ c) (princ " |"))
+ (princ "\n")))))
+ ;; Add back-end specific filters, but not user-defined ones. In
+ ;; particular, make sure to call parse-tree filters on the
+ ;; table.
+ (setq info
+ (let ((org-export-filters-alist nil))
+ (org-export-install-filters
+ (org-combine-plists
+ (org-export-get-environment backend nil params)
+ `(:back-end ,(org-export-get-backend backend))))))
+ (setq data
+ (org-export-filter-apply-functions
+ (plist-get info :filter-parse-tree)
+ (org-element-map (org-element-parse-buffer) 'table
+ #'identity nil t)
+ info)))
+ (when (and backend (symbolp backend) (not (org-export-get-backend backend)))
+ (user-error "Unknown :backend value"))
+ (when (or (not backend) (plist-get info :raw)) (require 'ox-org))
+ ;; Handle :skip parameter.
+ (let ((skip (plist-get info :skip)))
+ (when skip
+ (unless (wholenump skip) (user-error "Wrong :skip value"))
+ (let ((n 0))
+ (org-element-map data 'table-row
+ (lambda (row)
+ (if (>= n skip) t
+ (org-element-extract-element row)
+ (cl-incf n)
+ nil))
+ nil t))))
+ ;; Handle :skipcols parameter.
+ (let ((skipcols (plist-get info :skipcols)))
+ (when skipcols
+ (unless (consp skipcols) (user-error "Wrong :skipcols value"))
+ (org-element-map data 'table
+ (lambda (table)
+ (let ((specialp (org-export-table-has-special-column-p table)))
+ (dolist (row (org-element-contents table))
+ (when (eq (org-element-property :type row) 'standard)
+ (let ((c 1))
+ (dolist (cell (nthcdr (if specialp 1 0)
+ (org-element-contents row)))
+ (when (memq c skipcols)
+ (org-element-extract-element cell))
+ (cl-incf c))))))))))
+ ;; Since we are going to export using a low-level mechanism,
+ ;; ignore special column and special rows manually.
+ (let ((special? (org-export-table-has-special-column-p data))
+ ignore)
+ (org-element-map data (if special? '(table-cell table-row) 'table-row)
+ (lambda (datum)
+ (when (if (eq (org-element-type datum) 'table-row)
+ (org-export-table-row-is-special-p datum nil)
+ (org-export-first-sibling-p datum nil))
+ (push datum ignore))))
+ (setq info (plist-put info :ignore-list ignore)))
+ ;; We use a low-level mechanism to export DATA so as to skip all
+ ;; usual pre-processing and post-processing, i.e., hooks, Babel
+ ;; code evaluation, include keywords and macro expansion. Only
+ ;; back-end specific filters are retained.
+ (let ((output (org-export-data-with-backend data custom-backend info)))
+ ;; Remove final newline.
+ (if (org-string-nw-p output) (substring-no-properties output 0 -1) ""))))
+
+(defun org-table--generic-apply (value name &optional with-cons &rest args)
+ (cond ((null value) nil)
+ ((functionp value) `(funcall ',value ,@args))
+ ((stringp value)
+ (cond ((consp (car args)) `(apply #'format ,value ,@args))
+ (args `(format ,value ,@args))
+ (t value)))
+ ((and with-cons (consp value))
+ `(let ((val (cadr (memq column ',value))))
+ (cond ((null val) contents)
+ ((stringp val) (format val ,@args))
+ ((functionp val) (funcall val ,@args))
+ (t (user-error "Wrong %s value" ,name)))))
+ (t (user-error "Wrong %s value" name))))
+
+(defun org-table--to-generic-table (params)
+ "Return custom table transcoder according to PARAMS.
+PARAMS is a plist. See `orgtbl-to-generic' for more
+information."
+ (let ((backend (plist-get params :backend))
+ (splice (plist-get params :splice))
+ (tstart (plist-get params :tstart))
+ (tend (plist-get params :tend)))
+ `(lambda (table contents info)
+ (concat
+ ,(and tstart (not splice)
+ `(concat ,(org-table--generic-apply tstart ":tstart") "\n"))
+ ,(if (or (not backend) tstart tend splice) 'contents
+ `(org-export-with-backend ',backend table contents info))
+ ,(org-table--generic-apply (and (not splice) tend) ":tend")))))
+
+(defun org-table--to-generic-row (params)
+ "Return custom table row transcoder according to PARAMS.
+PARAMS is a plist. See `orgtbl-to-generic' for more
+information."
+ (let* ((backend (plist-get params :backend))
+ (lstart (plist-get params :lstart))
+ (llstart (plist-get params :llstart))
+ (hlstart (plist-get params :hlstart))
+ (hllstart (plist-get params :hllstart))
+ (lend (plist-get params :lend))
+ (llend (plist-get params :llend))
+ (hlend (plist-get params :hlend))
+ (hllend (plist-get params :hllend))
+ (lfmt (plist-get params :lfmt))
+ (llfmt (plist-get params :llfmt))
+ (hlfmt (plist-get params :hlfmt))
+ (hllfmt (plist-get params :hllfmt)))
+ `(lambda (row contents info)
+ (if (eq (org-element-property :type row) 'rule)
+ ,(cond
+ ((plist-member params :hline)
+ (org-table--generic-apply (plist-get params :hline) ":hline"))
+ (backend `(org-export-with-backend ',backend row nil info)))
+ (let ((headerp (org-export-table-row-in-header-p row info))
+ (lastp (not (org-export-get-next-element row info)))
+ (last-header-p (org-export-table-row-ends-header-p row info)))
+ (when contents
+ ;; Check if we can apply `:lfmt', `:llfmt', `:hlfmt', or
+ ;; `:hllfmt' to CONTENTS. Otherwise, fallback on
+ ;; `:lstart', `:lend' and their relatives.
+ ,(let ((cells
+ '(org-element-map row 'table-cell
+ (lambda (cell)
+ ;; Export all cells, without separators.
+ ;;
+ ;; Use `org-export-data-with-backend'
+ ;; instead of `org-export-data' to eschew
+ ;; cached values, which
+ ;; ignore :orgtbl-ignore-sep parameter.
+ (org-export-data-with-backend
+ cell
+ (plist-get info :back-end)
+ (org-combine-plists info '(:orgtbl-ignore-sep t))))
+ info)))
+ `(cond
+ ,(and hllfmt
+ `(last-header-p ,(org-table--generic-apply
+ hllfmt ":hllfmt" nil cells)))
+ ,(and hlfmt
+ `(headerp ,(org-table--generic-apply
+ hlfmt ":hlfmt" nil cells)))
+ ,(and llfmt
+ `(lastp ,(org-table--generic-apply
+ llfmt ":llfmt" nil cells)))
+ (t
+ ,(if lfmt (org-table--generic-apply lfmt ":lfmt" nil cells)
+ `(concat
+ (cond
+ ,(and
+ (or hllstart hllend)
+ `(last-header-p
+ (concat
+ ,(org-table--generic-apply hllstart ":hllstart")
+ contents
+ ,(org-table--generic-apply hllend ":hllend"))))
+ ,(and
+ (or hlstart hlend)
+ `(headerp
+ (concat
+ ,(org-table--generic-apply hlstart ":hlstart")
+ contents
+ ,(org-table--generic-apply hlend ":hlend"))))
+ ,(and
+ (or llstart llend)
+ `(lastp
+ (concat
+ ,(org-table--generic-apply llstart ":llstart")
+ contents
+ ,(org-table--generic-apply llend ":llend"))))
+ (t
+ ,(cond
+ ((or lstart lend)
+ `(concat
+ ,(org-table--generic-apply lstart ":lstart")
+ contents
+ ,(org-table--generic-apply lend ":lend")))
+ (backend
+ `(org-export-with-backend
+ ',backend row contents info))
+ (t 'contents)))))))))))))))
+
+(defun org-table--to-generic-cell (params)
+ "Return custom table cell transcoder according to PARAMS.
+PARAMS is a plist. See `orgtbl-to-generic' for more
+information."
+ (let* ((backend (plist-get params :backend))
+ (efmt (plist-get params :efmt))
+ (fmt (plist-get params :fmt))
+ (hfmt (plist-get params :hfmt))
+ (sep (plist-get params :sep))
+ (hsep (plist-get params :hsep)))
+ `(lambda (cell contents info)
+ (let ((headerp (org-export-table-row-in-header-p
+ (org-export-get-parent-element cell) info))
+ (column (1+ (cdr (org-export-table-cell-address cell info)))))
+ ;; Make sure that contents are exported as Org data when :raw
+ ;; parameter is non-nil.
+ ,(when (and backend (plist-get params :raw))
+ `(setq contents
+ ;; Since we don't know what are the pseudo object
+ ;; types defined in backend, we cannot pass them to
+ ;; `org-element-interpret-data'. As a consequence,
+ ;; they will be treated as pseudo elements, and
+ ;; will have newlines appended instead of spaces.
+ ;; Therefore, we must make sure :post-blank value
+ ;; is really turned into spaces.
+ (replace-regexp-in-string
+ "\n" " "
+ (org-trim
+ (org-element-interpret-data
+ (org-element-contents cell))))))
+ (when contents
+ ;; Check if we can apply `:efmt' on CONTENTS.
+ ,(when efmt
+ `(when (string-match orgtbl-exp-regexp contents)
+ (let ((mantissa (match-string 1 contents))
+ (exponent (match-string 2 contents)))
+ (setq contents ,(org-table--generic-apply
+ efmt ":efmt" t 'mantissa 'exponent)))))
+ ;; Check if we can apply FMT (or HFMT) on CONTENTS.
+ (cond
+ ,(and hfmt `(headerp (setq contents ,(org-table--generic-apply
+ hfmt ":hfmt" t 'contents))))
+ ,(and fmt `(t (setq contents ,(org-table--generic-apply
+ fmt ":fmt" t 'contents))))))
+ ;; If a separator is provided, use it instead of BACKEND's.
+ ;; Separators are ignored when LFMT (or equivalent) is
+ ;; provided.
+ ,(cond
+ ((or hsep sep)
+ `(if (or ,(and (not sep) '(not headerp))
+ (plist-get info :orgtbl-ignore-sep)
+ (not (org-export-get-next-element cell info)))
+ ,(if (not backend) 'contents
+ `(org-export-with-backend ',backend cell contents info))
+ (concat contents
+ ,(if (and sep hsep) `(if headerp ,hsep ,sep)
+ (or hsep sep)))))
+ (backend `(org-export-with-backend ',backend cell contents info))
+ (t 'contents))))))
;;;###autoload
(defun orgtbl-to-tsv (table params)
"Convert the orgtbl-mode table to TAB separated material."
(orgtbl-to-generic table (org-combine-plists '(:sep "\t") params)))
+
;;;###autoload
(defun orgtbl-to-csv (table params)
"Convert the orgtbl-mode table to CSV material.
This does take care of the proper quoting of fields with comma or quotes."
- (orgtbl-to-generic table (org-combine-plists
- '(:sep "," :fmt org-quote-csv-field)
- params)))
+ (orgtbl-to-generic table
+ (org-combine-plists '(:sep "," :fmt org-quote-csv-field)
+ params)))
;;;###autoload
(defun orgtbl-to-latex (table params)
"Convert the orgtbl-mode TABLE to LaTeX.
-TABLE is a list, each entry either the symbol `hline' for a horizontal
-separator line, or a list of fields for that line.
-PARAMS is a property list of parameters that can influence the conversion.
-Supports all parameters from `orgtbl-to-generic'. Most important for
-LaTeX are:
-
-:splice When set to t, return only table body lines, don't wrap
- them into a tabular environment. Default is nil.
-
-:fmt A format to be used to wrap the field, should contain %s for the
- original field value. For example, to wrap everything in dollars,
- use :fmt \"$%s$\". This may also be a property list with column
- numbers and formats. For example :fmt (2 \"$%s$\" 4 \"%s%%\")
- The format may also be a function that formats its one argument.
-
-:efmt Format for transforming numbers with exponentials. The format
- should have %s twice for inserting mantissa and exponent, for
- example \"%s\\\\times10^{%s}\". LaTeX default is \"%s\\\\,(%s)\".
- This may also be a property list with column numbers and formats.
- The format may also be a function that formats its two arguments.
-
-:llend If you find too much space below the last line of a table,
- pass a value of \"\" for :llend to suppress the final \\\\.
-
-The general parameters :skip and :skipcols have already been applied when
-this function is called."
- (let* ((alignment (mapconcat (lambda (x) (if x "r" "l"))
- org-table-last-alignment ""))
- (params2
- (list
- :tstart (concat "\\begin{tabular}{" alignment "}")
- :tend "\\end{tabular}"
- :lstart "" :lend " \\\\" :sep " & "
- :efmt "%s\\,(%s)" :hline "\\hline")))
- (require 'ox-latex)
- (orgtbl-to-generic table (org-combine-plists params2 params) 'latex)))
+
+TABLE is a list, each entry either the symbol `hline' for
+a horizontal separator line, or a list of fields for that line.
+PARAMS is a property list of parameters that can influence the
+conversion. All parameters from `orgtbl-to-generic' are
+supported. It is also possible to use the following ones:
+
+:booktabs
+
+ When non-nil, use formal \"booktabs\" style.
+
+:environment
+
+ Specify environment to use, as a string. If you use
+ \"longtable\", you may also want to specify :language property,
+ as a string, to get proper continuation strings."
+ (require 'ox-latex)
+ (orgtbl-to-generic
+ table
+ (org-combine-plists
+ ;; Provide sane default values.
+ (list :backend 'latex
+ :latex-default-table-mode 'table
+ :latex-tables-centered nil
+ :latex-tables-booktabs (plist-get params :booktabs)
+ :latex-table-scientific-notation nil
+ :latex-default-table-environment
+ (or (plist-get params :environment) "tabular"))
+ params)))
;;;###autoload
(defun orgtbl-to-html (table params)
"Convert the orgtbl-mode TABLE to HTML.
-TABLE is a list, each entry either the symbol `hline' for a horizontal
-separator line, or a list of fields for that line.
-PARAMS is a property list of parameters that can influence the conversion.
-Currently this function recognizes the following parameters:
-:splice When set to t, return only table body lines, don't wrap
- them into a
environment. Default is nil.
+TABLE is a list, each entry either the symbol `hline' for
+a horizontal separator line, or a list of fields for that line.
+PARAMS is a property list of parameters that can influence the
+conversion. All parameters from `orgtbl-to-generic' are
+supported. It is also possible to use the following one:
-The general parameters :skip and :skipcols have already been applied when
-this function is called. The function does *not* use `orgtbl-to-generic',
-so you cannot specify parameters for it."
+:attributes
+
+ Attributes and values, as a plist, which will be used in
+
")
+ :html-table-use-header-tags-for-first-column nil
+ :html-table-align-individual-fields t
+ :html-table-row-tags '("
" . "
")
+ :html-table-attributes
+ (if (plist-member params :attributes)
+ (plist-get params :attributes)
+ '(:border "2" :cellspacing "0" :cellpadding "6" :rules "groups"
+ :frame "hsides")))
+ params)))
;;;###autoload
(defun orgtbl-to-texinfo (table params)
- "Convert the orgtbl-mode TABLE to TeXInfo.
-TABLE is a list, each entry either the symbol `hline' for a horizontal
-separator line, or a list of fields for that line.
-PARAMS is a property list of parameters that can influence the conversion.
-Supports all parameters from `orgtbl-to-generic'. Most important for
-TeXInfo are:
-
-:splice nil/t When set to t, return only table body lines, don't wrap
- them into a multitable environment. Default is nil.
-
-:fmt fmt A format to be used to wrap the field, should contain
- %s for the original field value. For example, to wrap
- everything in @kbd{}, you could use :fmt \"@kbd{%s}\".
- This may also be a property list with column numbers and
- formats. For example :fmt (2 \"@kbd{%s}\" 4 \"@code{%s}\").
- Each format also may be a function that formats its one
- argument.
-
-:cf \"f1 f2..\" The column fractions for the table. By default these
- are computed automatically from the width of the columns
- under org-mode.
-
-The general parameters :skip and :skipcols have already been applied when
-this function is called."
- (let* ((total (float (apply '+ org-table-last-column-widths)))
- (colfrac (or (plist-get params :cf)
- (mapconcat
- (lambda (x) (format "%.3f" (/ (float x) total)))
- org-table-last-column-widths " ")))
- (params2
- (list
- :tstart (concat "@multitable @columnfractions " colfrac)
- :tend "@end multitable"
- :lstart "@item " :lend "" :sep " @tab "
- :hlstart "@headitem ")))
- (require 'ox-texinfo)
- (orgtbl-to-generic table (org-combine-plists params2 params) 'texinfo)))
+ "Convert the orgtbl-mode TABLE to Texinfo.
+
+TABLE is a list, each entry either the symbol `hline' for
+a horizontal separator line, or a list of fields for that line.
+PARAMS is a property list of parameters that can influence the
+conversion. All parameters from `orgtbl-to-generic' are
+supported. It is also possible to use the following one:
+
+:columns
+
+ Column widths, as a string. When providing column fractions,
+ \"@columnfractions\" command can be omitted."
+ (require 'ox-texinfo)
+ (let ((output
+ (orgtbl-to-generic
+ table
+ (org-combine-plists
+ (list :backend 'texinfo
+ :texinfo-tables-verbatim nil
+ :texinfo-table-scientific-notation nil)
+ params)))
+ (columns (let ((w (plist-get params :columns)))
+ (cond ((not w) nil)
+ ((string-match-p "{\\|@columnfractions " w) w)
+ (t (concat "@columnfractions " w))))))
+ (if (not columns) output
+ (replace-regexp-in-string
+ "@multitable \\(.*\\)" columns output t nil 1))))
;;;###autoload
(defun orgtbl-to-orgtbl (table params)
"Convert the orgtbl-mode TABLE into another orgtbl-mode table.
+
+TABLE is a list, each entry either the symbol `hline' for
+a horizontal separator line, or a list of fields for that line.
+PARAMS is a property list of parameters that can influence the
+conversion. All parameters from `orgtbl-to-generic' are
+supported.
+
Useful when slicing one table into many. The :hline, :sep,
-:lstart, and :lend provide orgtbl framing. The default nil :tstart
-and :tend suppress strings without splicing; they can be set to
-provide ORGTBL directives for the generated table."
- (let* ((params2
- (list
- :remove-newlines t
- :tstart nil :tend nil
- :hline "|---"
- :sep " | "
- :lstart "| "
- :lend " |"))
- (params (org-combine-plists params2 params)))
- (with-temp-buffer
- (insert (orgtbl-to-generic table params))
- (goto-char (point-min))
- (while (re-search-forward org-table-hline-regexp nil t)
- (org-table-align))
- (buffer-substring 1 (buffer-size)))))
+:lstart, and :lend provide orgtbl framing. :tstart and :tend can
+be set to provide ORGTBL directives for the generated table."
+ (require 'ox-org)
+ (orgtbl-to-generic table (org-combine-plists params (list :backend 'org))))
(defun orgtbl-to-table.el (table params)
- "Convert the orgtbl-mode TABLE into a table.el table."
+ "Convert the orgtbl-mode TABLE into a table.el table.
+TABLE is a list, each entry either the symbol `hline' for
+a horizontal separator line, or a list of fields for that line.
+PARAMS is a property list of parameters that can influence the
+conversion. All parameters from `orgtbl-to-generic' are
+supported."
(with-temp-buffer
(insert (orgtbl-to-orgtbl table params))
(org-table-align)
@@ -4920,19 +5256,137 @@ provide ORGTBL directives for the generated table."
(defun orgtbl-to-unicode (table params)
"Convert the orgtbl-mode TABLE into a table with unicode characters.
-You need the ascii-art-to-unicode.el package for this. You can download
-it here: http://gnuvola.org/software/j/aa2u/ascii-art-to-unicode.el."
- (with-temp-buffer
- (insert (orgtbl-to-table.el table params))
- (goto-char (point-min))
- (if (or (featurep 'ascii-art-to-unicode)
- (require 'ascii-art-to-unicode nil t))
- (aa2u)
- (unless (delq nil (mapcar (lambda (l) (string-match "aa2u" (car l))) org-stored-links))
- (push '("http://gnuvola.org/software/j/aa2u/ascii-art-to-unicode.el"
- "Link to ascii-art-to-unicode.el") org-stored-links))
- (user-error "Please download ascii-art-to-unicode.el (use C-c C-l to insert the link to it)"))
- (buffer-string)))
+
+TABLE is a list, each entry either the symbol `hline' for
+a horizontal separator line, or a list of fields for that line.
+PARAMS is a property list of parameters that can influence the
+conversion. All parameters from `orgtbl-to-generic' are
+supported. It is also possible to use the following ones:
+
+:ascii-art
+
+ When non-nil, use \"ascii-art-to-unicode\" package to translate
+ the table. You can download it here:
+ http://gnuvola.org/software/j/aa2u/ascii-art-to-unicode.el.
+
+:narrow
+
+ When non-nil, narrow columns width than provided width cookie,
+ using \"=>\" as an ellipsis, just like in an Org mode buffer."
+ (require 'ox-ascii)
+ (orgtbl-to-generic
+ table
+ (org-combine-plists
+ (list :backend 'ascii
+ :ascii-charset 'utf-8
+ :ascii-table-widen-columns (not (plist-get params :narrow))
+ :ascii-table-use-ascii-art (plist-get params :ascii-art))
+ params)))
+
+;; Put the cursor in a column containing numerical values
+;; of an Org table,
+;; type C-c " a
+;; A new column is added with a bar plot.
+;; When the table is refreshed (C-u C-c *),
+;; the plot is updated to reflect the new values.
+
+(defun orgtbl-ascii-draw (value min max &optional width characters)
+ "Draw an ascii bar in a table.
+VALUE is the value to plot, it determines the width of the bar to draw.
+MIN is the value that will be displayed as empty (zero width bar).
+MAX is the value that will draw a bar filling all the WIDTH.
+WIDTH is the span in characters from MIN to MAX.
+CHARACTERS is a string that will compose the bar, with shades of grey
+from pure white to pure black. It defaults to a 10 characters string
+of regular ascii characters."
+ (let* ((width (ceiling (or width 12)))
+ (characters (or characters " .:;c!lhVHW"))
+ (len (1- (length characters)))
+ (value (float (if (numberp value)
+ value (string-to-number value))))
+ (relative (/ (- value min) (- max min)))
+ (steps (round (* relative width len))))
+ (cond ((< steps 0) "too small")
+ ((> steps (* width len)) "too large")
+ (t (let* ((int-division (/ steps len))
+ (remainder (- steps (* int-division len))))
+ (concat (make-string int-division (elt characters len))
+ (string (elt characters remainder))))))))
+
+;;;###autoload
+(defun orgtbl-ascii-plot (&optional ask)
+ "Draw an ASCII bar plot in a column.
+
+With cursor in a column containing numerical values, this function
+will draw a plot in a new column.
+
+ASK, if given, is a numeric prefix to override the default 12
+characters width of the plot. ASK may also be the `\\[universal-argument]' \
+prefix,
+which will prompt for the width."
+ (interactive "P")
+ (let ((col (org-table-current-column))
+ (min 1e999) ; 1e999 will be converted to infinity
+ (max -1e999) ; which is the desired result
+ (table (org-table-to-lisp))
+ (length
+ (cond ((consp ask)
+ (read-number "Length of column " 12))
+ ((numberp ask) ask)
+ (t 12))))
+ ;; Skip any hline a the top of table.
+ (while (eq (car table) 'hline) (setq table (cdr table)))
+ ;; Skip table header if any.
+ (dolist (x (or (cdr (memq 'hline table)) table))
+ (when (consp x)
+ (setq x (nth (1- col) x))
+ (when (string-match
+ "^[-+]?\\([0-9]*[.]\\)?[0-9]*\\([eE][+-]?[0-9]+\\)?$"
+ x)
+ (setq x (string-to-number x))
+ (when (> min x) (setq min x))
+ (when (< max x) (setq max x)))))
+ (org-table-insert-column)
+ (org-table-move-column-right)
+ (org-table-store-formulas
+ (cons
+ (cons
+ (concat "$" (number-to-string (1+ col)))
+ (format "'(%s $%s %s %s %s)"
+ "orgtbl-ascii-draw" col min max length))
+ (org-table-get-stored-formulas)))
+ (org-table-recalculate t)))
+
+;; Example of extension: unicode characters
+;; Here are two examples of different styles.
+
+;; Unicode block characters are used to give a smooth effect.
+;; See http://en.wikipedia.org/wiki/Block_Elements
+;; Use one of those drawing functions
+;; - orgtbl-ascii-draw (the default ascii)
+;; - orgtbl-uc-draw-grid (unicode with a grid effect)
+;; - orgtbl-uc-draw-cont (smooth unicode)
+
+;; This is best viewed with the "DejaVu Sans Mono" font
+;; (use M-x set-default-font).
+
+(defun orgtbl-uc-draw-grid (value min max &optional width)
+ "Draw a bar in a table using block unicode characters.
+It is a variant of orgtbl-ascii-draw with Unicode block
+characters, for a smooth display. Bars appear as grids (to the
+extent the font allows)."
+ ;; http://en.wikipedia.org/wiki/Block_Elements
+ ;; best viewed with the "DejaVu Sans Mono" font.
+ (orgtbl-ascii-draw value min max width
+ " \u258F\u258E\u258D\u258C\u258B\u258A\u2589"))
+
+(defun orgtbl-uc-draw-cont (value min max &optional width)
+ "Draw a bar in a table using block unicode characters.
+It is a variant of orgtbl-ascii-draw with Unicode block
+characters, for a smooth display. Bars are solid (to the extent
+the font allows)."
+ (orgtbl-ascii-draw value min max width
+ " \u258F\u258E\u258D\u258C\u258B\u258A\u2589\u2588"))
(defun org-table-get-remote-range (name-or-id form)
"Get a field value or a list of values in a range from table at ID.
@@ -4949,57 +5403,74 @@ The return value is either a single string for a single field, or a
list of the fields in the rectangle."
(save-match-data
(let ((case-fold-search t) (id-loc nil)
- ;; Protect a bunch of variables from being overwritten
- ;; by the context of the remote table
+ ;; Protect a bunch of variables from being overwritten by
+ ;; the context of the remote table.
org-table-column-names org-table-column-name-regexp
org-table-local-parameters org-table-named-field-locations
- org-table-current-line-types org-table-current-begin-line
+ org-table-current-line-types
org-table-current-begin-pos org-table-dlines
org-table-current-ncol
org-table-hlines org-table-last-alignment
org-table-last-column-widths org-table-last-alignment
- org-table-last-column-widths tbeg
+ org-table-last-column-widths
buffer loc)
(setq form (org-table-convert-refs-to-rc form))
- (save-excursion
- (save-restriction
- (widen)
- (save-excursion
- (goto-char (point-min))
- (if (re-search-forward
- (concat "^[ \t]*#\\+\\(tbl\\)?name:[ \t]*"
- (regexp-quote name-or-id) "[ \t]*$")
- nil t)
- (setq buffer (current-buffer) loc (match-beginning 0))
- (setq id-loc (org-id-find name-or-id 'marker))
- (unless (and id-loc (markerp id-loc))
- (user-error "Can't find remote table \"%s\"" name-or-id))
- (setq buffer (marker-buffer id-loc)
- loc (marker-position id-loc))
- (move-marker id-loc nil)))
- (with-current-buffer buffer
- (save-excursion
- (save-restriction
- (widen)
- (goto-char loc)
- (forward-char 1)
- (unless (and (re-search-forward "^\\(\\*+ \\)\\|[ \t]*|" nil t)
- (not (match-beginning 1)))
- (user-error "Cannot find a table at NAME or ID %s" name-or-id))
- (setq tbeg (point-at-bol))
- (org-table-get-specials)
- (setq form (org-table-formula-substitute-names
- (org-table-formula-handle-first/last-rc form)))
- (if (and (string-match org-table-range-regexp form)
- (> (length (match-string 0 form)) 1))
- (save-match-data
- (org-table-get-range (match-string 0 form) tbeg 1))
- form)))))))))
+ (org-with-wide-buffer
+ (goto-char (point-min))
+ (if (re-search-forward
+ (concat "^[ \t]*#\\+\\(tbl\\)?name:[ \t]*"
+ (regexp-quote name-or-id) "[ \t]*$")
+ nil t)
+ (setq buffer (current-buffer) loc (match-beginning 0))
+ (setq id-loc (org-id-find name-or-id 'marker))
+ (unless (and id-loc (markerp id-loc))
+ (user-error "Can't find remote table \"%s\"" name-or-id))
+ (setq buffer (marker-buffer id-loc)
+ loc (marker-position id-loc))
+ (move-marker id-loc nil))
+ (with-current-buffer buffer
+ (org-with-wide-buffer
+ (goto-char loc)
+ (forward-char 1)
+ (unless (and (re-search-forward "^\\(\\*+ \\)\\|^[ \t]*|" nil t)
+ (not (match-beginning 1)))
+ (user-error "Cannot find a table at NAME or ID %s" name-or-id))
+ (org-table-analyze)
+ (setq form (org-table-formula-substitute-names
+ (org-table-formula-handle-first/last-rc form)))
+ (if (and (string-match org-table-range-regexp form)
+ (> (length (match-string 0 form)) 1))
+ (org-table-get-range
+ (match-string 0 form) org-table-current-begin-pos 1)
+ form)))))))
+
+(defun org-table-remote-reference-indirection (form)
+ "Return formula with table remote references substituted by indirection.
+For example \"remote($1, @>$2)\" => \"remote(year_2013, @>$1)\".
+This indirection works only with the format @ROW$COLUMN. The
+format \"B3\" is not supported because it can not be
+distinguished from a plain table name or ID."
+ (let ((regexp
+ ;; Same as in `org-table-eval-formula'.
+ (concat "\\"))
(force-mode-line-update)))
-(defun org-timer-cancel-timer ()
- "Cancel the current timer."
- (interactive)
- (when (eval org-timer-current-timer)
- (run-hooks 'org-timer-cancel-hook)
- (cancel-timer org-timer-current-timer)
- (setq org-timer-current-timer nil)
- (setq org-timer-timer-is-countdown nil)
- (org-timer-set-mode-line 'off))
- (message "Last timer canceled"))
-
(defun org-timer-show-remaining-time ()
"Display the remaining time before the timer ends."
(interactive)
(require 'time)
- (if (not org-timer-current-timer)
+ (if (not org-timer-countdown-timer)
(message "No timer set")
(let* ((rtime (decode-time
- (time-subtract (timer--time org-timer-current-timer)
+ (time-subtract (timer--time org-timer-countdown-timer)
(current-time))))
(rsecs (nth 0 rtime))
(rmins (nth 1 rtime)))
(message "%d minute(s) %d seconds left before next time out"
rmins rsecs))))
-(defvar org-clock-sound)
-
;;;###autoload
(defun org-timer-set-timer (&optional opt)
- "Prompt for a duration and set a timer.
+ "Prompt for a duration in minutes or hh:mm:ss and set a timer.
-If `org-timer-default-timer' is not zero, suggest this value as
+If `org-timer-default-timer' is not \"0\", suggest this value as
the default duration for the timer. If a timer is already set,
prompt the user if she wants to replace it.
Called with a numeric prefix argument, use this numeric value as
-the duration of the timer.
+the duration of the timer in minutes.
Called with a `C-u' prefix arguments, use `org-timer-default-timer'
without prompting the user for a duration.
With two `C-u' prefix arguments, use `org-timer-default-timer'
without prompting the user for a duration and automatically
-replace any running timer."
+replace any running timer.
+
+By default, the timer duration will be set to the number of
+minutes in the Effort property, if any. You can ignore this by
+using three `C-u' prefix arguments."
(interactive "P")
- (let ((minutes (or (and (numberp opt) (number-to-string opt))
- (and (listp opt) (not (null opt))
- (number-to-string org-timer-default-timer))
- (read-from-minibuffer
- "How many minutes left? "
- (if (not (eq org-timer-default-timer 0))
- (number-to-string org-timer-default-timer))))))
+ (when (and org-timer-start-time
+ (not org-timer-countdown-timer))
+ (user-error "Relative timer is running. Stop first"))
+ (let* ((default-timer
+ ;; `org-timer-default-timer' used to be a number, don't choke:
+ (if (numberp org-timer-default-timer)
+ (number-to-string org-timer-default-timer)
+ org-timer-default-timer))
+ (effort-minutes (ignore-errors (org-get-at-eol 'effort-minutes 1)))
+ (minutes (or (and (numberp opt) (number-to-string opt))
+ (and (not (equal opt '(64)))
+ effort-minutes
+ (number-to-string effort-minutes))
+ (and (consp opt) default-timer)
+ (and (stringp opt) opt)
+ (read-from-minibuffer
+ "How much time left? (minutes or h:mm:ss) "
+ (and (not (string-equal default-timer "0")) default-timer)))))
+ (when (string-match "\\`[0-9]+\\'" minutes)
+ (setq minutes (concat minutes ":00")))
(if (not (string-match "[0-9]+" minutes))
(org-timer-show-remaining-time)
- (let* ((mins (string-to-number (match-string 0 minutes)))
- (secs (* mins 60))
- (hl (cond
- ((string-match "Org Agenda" (buffer-name))
- (let* ((marker (or (get-text-property (point) 'org-marker)
- (org-agenda-error)))
- (hdmarker (or (get-text-property (point) 'org-hd-marker)
- marker))
- (pos (marker-position marker)))
- (with-current-buffer (marker-buffer marker)
- (widen)
- (goto-char pos)
- (org-show-entry)
- (or (ignore-errors (org-get-heading))
- (concat "File:" (file-name-nondirectory (buffer-file-name)))))))
- ((derived-mode-p 'org-mode)
- (or (ignore-errors (org-get-heading))
- (concat "File:" (file-name-nondirectory (buffer-file-name)))))
- (t (error "Not in an Org buffer"))))
- timer-set)
- (if (or (and org-timer-current-timer
- (or (equal opt '(16))
- (y-or-n-p "Replace current timer? ")))
- (not org-timer-current-timer))
- (progn
- (require 'org-clock)
- (when org-timer-current-timer
- (cancel-timer org-timer-current-timer))
- (setq org-timer-current-timer
- (run-with-timer
- secs nil `(lambda ()
- (setq org-timer-current-timer nil)
- (org-notify ,(format "%s: time out" hl) ,org-clock-sound)
- (setq org-timer-timer-is-countdown nil)
- (org-timer-set-mode-line 'off)
- (run-hooks 'org-timer-done-hook))))
- (run-hooks 'org-timer-set-hook)
- (setq org-timer-timer-is-countdown t
- org-timer-start-time
- (time-add (current-time) (seconds-to-time (* mins 60))))
- (org-timer-set-mode-line 'on))
- (message "No timer set"))))))
+ (let ((secs (org-timer-hms-to-secs (org-timer-fix-incomplete minutes))))
+ (if (and org-timer-countdown-timer
+ (not (or (equal opt '(16))
+ (y-or-n-p "Replace current timer? "))))
+ (message "No timer set")
+ (when (timerp org-timer-countdown-timer)
+ (cancel-timer org-timer-countdown-timer))
+ (setq org-timer-countdown-timer-title
+ (org-timer--get-timer-title))
+ (setq org-timer-countdown-timer
+ (org-timer--run-countdown-timer
+ secs org-timer-countdown-timer-title))
+ (run-hooks 'org-timer-set-hook)
+ (setq org-timer-start-time
+ (time-add (current-time) (seconds-to-time secs)))
+ (setq org-timer-pause-time nil)
+ (org-timer-set-mode-line 'on))))))
+
+(defun org-timer--run-countdown-timer (secs title)
+ "Start countdown timer that will last SECS.
+TITLE will be appended to the notification message displayed when
+time is up."
+ (let ((msg (format "%s: time out" title)))
+ (run-with-timer
+ secs nil `(lambda ()
+ (setq org-timer-countdown-timer nil
+ org-timer-start-time nil)
+ (org-notify ,msg ,org-clock-sound)
+ (org-timer-set-mode-line 'off)
+ (run-hooks 'org-timer-done-hook)))))
+
+(defun org-timer--get-timer-title ()
+ "Construct timer title from heading or file name of Org buffer."
+ (cond
+ ((derived-mode-p 'org-agenda-mode)
+ (let* ((marker (or (get-text-property (point) 'org-marker)
+ (org-agenda-error)))
+ (hdmarker (or (get-text-property (point) 'org-hd-marker)
+ marker)))
+ (with-current-buffer (marker-buffer marker)
+ (org-with-wide-buffer
+ (goto-char hdmarker)
+ (org-show-entry)
+ (or (ignore-errors (org-get-heading))
+ (buffer-name (buffer-base-buffer)))))))
+ ((derived-mode-p 'org-mode)
+ (or (ignore-errors (org-get-heading))
+ (buffer-name (buffer-base-buffer))))
+ (t (error "Not in an Org buffer"))))
(provide 'org-timer)
diff --git a/lisp/org/org-version.el b/lisp/org/org-version.el
index aae65cc6d37..2db3eae2d8a 100644
--- a/lisp/org/org-version.el
+++ b/lisp/org/org-version.el
@@ -3,15 +3,15 @@
;;; Code:
;;;###autoload
(defun org-release ()
- "The release version of org-mode.
- Inserted by installing org-mode or when a release is made."
- (let ((org-release "8.2.10"))
+ "The release version of Org.
+Inserted by installing Org mode or when a release is made."
+ (let ((org-release "9.0.9"))
org-release))
;;;###autoload
(defun org-git-version ()
"The Git version of org-mode.
- Inserted by installing org-mode or when a release is made."
- (let ((org-git-version "release_8.2.10"))
+Inserted by installing Org or when a release is made."
+ (let ((org-git-version "release_9.0.9"))
org-git-version))
(provide 'org-version)
diff --git a/lisp/org/org-w3m.el b/lisp/org/org-w3m.el
index 8360bd07fe4..e9bbeff37c4 100644
--- a/lisp/org/org-w3m.el
+++ b/lisp/org/org-w3m.el
@@ -1,4 +1,4 @@
-;;; org-w3m.el --- Support from copy and paste from w3m to Org-mode
+;;; org-w3m.el --- Support from Copy and Paste From w3m -*- lexical-binding: t; -*-
;; Copyright (C) 2008-2017 Free Software Foundation, Inc.
@@ -25,9 +25,9 @@
;;; Commentary:
;; This file implements copying HTML content from a w3m buffer and
-;; transforming the text on the fly so that it can be pasted into
-;; an org-mode buffer with hot links. It will also work for regions
-;; in gnus buffers that have been washed with w3m.
+;; transforming the text on the fly so that it can be pasted into an
+;; Org buffer with hot links. It will also work for regions in gnus
+;; buffers that have been washed with w3m.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
@@ -35,7 +35,7 @@
;; Richard Riley
;;
-;; The idea of transforming the HTML content with org-mode style is
+;; The idea of transforming the HTML content with Org syntax is
;; proposed by Richard, I'm just coding it.
;;
@@ -46,7 +46,7 @@
(defvar w3m-current-url)
(defvar w3m-current-title)
-(add-hook 'org-store-link-functions 'org-w3m-store-link)
+(org-link-set-parameters "w3m" :store #'org-w3m-store-link)
(defun org-w3m-store-link ()
"Store a link to a w3m buffer."
(when (eq major-mode 'w3m-mode)
@@ -60,7 +60,7 @@
"Copy current buffer content or active region with `org-mode' style links.
This will encode `link-title' and `link-location' with
`org-make-link-string', and insert the transformed test into the kill ring,
-so that it can be yanked into an Org-mode buffer with links working correctly."
+so that it can be yanked into an Org buffer with links working correctly."
(interactive)
(let* ((regionp (org-region-active-p))
(transform-start (point-min))
@@ -107,7 +107,7 @@ so that it can be yanked into an Org-mode buffer with links working correctly."
(concat return-content
(buffer-substring (point) transform-end))))
(org-kill-new return-content)
- (message "Transforming links...done, use C-y to insert text into Org-mode file")
+ (message "Transforming links...done, use C-y to insert text into Org file")
(message "Copy with link transformation complete."))))
(defun org-w3m-get-anchor-start ()
diff --git a/lisp/org/org.el b/lisp/org/org.el
index 02a7a0c09af..5d10eed1511 100644
--- a/lisp/org/org.el
+++ b/lisp/org/org.el
@@ -1,4 +1,4 @@
-;;; org.el --- Outline-based notes management and organizer
+;;; org.el --- Outline-based notes management and organizer -*- lexical-binding: t; -*-
;; Carstens outline-mode for keeping track of everything.
;; Copyright (C) 2004-2017 Free Software Foundation, Inc.
@@ -25,23 +25,24 @@
;;
;;; Commentary:
;;
-;; Org-mode is a mode for keeping notes, maintaining ToDo lists, and doing
+;; Org is a mode for keeping notes, maintaining ToDo lists, and doing
;; project planning with a fast and effective plain-text system.
;;
-;; Org-mode develops organizational tasks around NOTES files that contain
-;; information about projects as plain text. Org-mode is implemented on
-;; top of outline-mode, which makes it possible to keep the content of
-;; large files well structured. Visibility cycling and structure editing
-;; help to work with the tree. Tables are easily created with a built-in
-;; table editor. Org-mode supports ToDo items, deadlines, time stamps,
-;; and scheduling. It dynamically compiles entries into an agenda that
-;; utilizes and smoothly integrates much of the Emacs calendar and diary.
-;; Plain text URL-like links connect to websites, emails, Usenet
-;; messages, BBDB entries, and any files related to the projects. For
-;; printing and sharing of notes, an Org-mode file can be exported as a
-;; structured ASCII file, as HTML, or (todo and agenda items only) as an
-;; iCalendar file. It can also serve as a publishing tool for a set of
-;; linked webpages.
+;; Org mode develops organizational tasks around NOTES files that
+;; contain information about projects as plain text. Org mode is
+;; implemented on top of outline-mode, which makes it possible to keep
+;; the content of large files well structured. Visibility cycling and
+;; structure editing help to work with the tree. Tables are easily
+;; created with a built-in table editor. Org mode supports ToDo
+;; items, deadlines, time stamps, and scheduling. It dynamically
+;; compiles entries into an agenda that utilizes and smoothly
+;; integrates much of the Emacs calendar and diary. Plain text
+;; URL-like links connect to websites, emails, Usenet messages, BBDB
+;; entries, and any files related to the projects. For printing and
+;; sharing of notes, an Org file can be exported as a structured ASCII
+;; file, as HTML, or (todo and agenda items only) as an iCalendar
+;; file. It can also serve as a publishing tool for a set of linked
+;; webpages.
;;
;; Installation and Activation
;; ---------------------------
@@ -51,11 +52,11 @@
;;
;; Documentation
;; -------------
-;; The documentation of Org-mode can be found in the TeXInfo file. The
+;; The documentation of Org mode can be found in the TeXInfo file. The
;; distribution also contains a PDF version of it. At the homepage of
-;; Org-mode, you can read the same text online as HTML. There is also an
+;; Org mode, you can read the same text online as HTML. There is also an
;; excellent reference card made by Philip Rooke. This card can be found
-;; in the etc/ directory of Emacs 22.
+;; in the doc/ directory.
;;
;; A list of recent changes can be found at
;; http://orgmode.org/Changes.html
@@ -63,21 +64,29 @@
;;; Code:
(defvar org-inhibit-highlight-removal nil) ; dynamically scoped param
-(defvar org-table-formula-constants-local nil
+(defvar-local org-table-formula-constants-local nil
"Local version of `org-table-formula-constants'.")
-(make-variable-buffer-local 'org-table-formula-constants-local)
;;;; Require other packages
-(eval-when-compile
- (require 'cl)
- (require 'gnus-sum))
+(require 'cl-lib)
+
+(eval-when-compile (require 'gnus-sum))
(require 'calendar)
(require 'find-func)
(require 'format-spec)
-(load "org-loaddefs.el" t t t)
+(or (eq this-command 'eval-buffer)
+ (condition-case nil
+ (load (concat (file-name-directory load-file-name)
+ "org-loaddefs.el")
+ nil t t t)
+ (error
+ (message "WARNING: No org-loaddefs.el file could be found from where org.el is loaded.")
+ (sit-for 3)
+ (message "You need to run \"make\" or \"make autoloads\" from Org lisp directory")
+ (sit-for 3))))
(require 'org-macs)
(require 'org-compat)
@@ -101,75 +110,87 @@ sure that we are at the beginning of the line.")
"Matches a headline, putting stars and text into groups.
Stars are put in group 1 and the trimmed body in group 2.")
-;; Emacs 22 calendar compatibility: Make sure the new variables are available
-(unless (boundp 'calendar-view-holidays-initially-flag)
- (org-defvaralias 'calendar-view-holidays-initially-flag
- 'view-calendar-holidays-initially))
-(unless (boundp 'calendar-view-diary-initially-flag)
- (org-defvaralias 'calendar-view-diary-initially-flag
- 'view-diary-entries-initially))
-(unless (boundp 'diary-fancy-buffer)
- (org-defvaralias 'diary-fancy-buffer 'fancy-diary-buffer))
-
+(declare-function calendar-check-holidays "holidays" (date))
+(declare-function cdlatex-environment "ext:cdlatex" (environment item))
+(declare-function isearch-no-upper-case-p "isearch" (string regexp-flag))
(declare-function org-add-archive-files "org-archive" (files))
-
-(declare-function org-inlinetask-at-task-p "org-inlinetask" ())
-(declare-function org-inlinetask-outline-regexp "org-inlinetask" ())
-(declare-function org-inlinetask-toggle-visibility "org-inlinetask" ())
-(declare-function org-pop-to-buffer-same-window "org-compat" (&optional buffer-or-name norecord label))
+(declare-function org-agenda-entry-get-agenda-timestamp "org-agenda" (pom))
+(declare-function org-agenda-list "org-agenda" (&optional arg start-day span with-hour))
+(declare-function org-agenda-redo "org-agenda" (&optional all))
+(declare-function org-babel-do-in-edit-buffer "ob-core" (&rest body) t)
+(declare-function org-babel-tangle-file "ob-tangle" (file &optional target-file lang))
+(declare-function org-beamer-mode "ox-beamer" (&optional prefix) t)
(declare-function org-clock-get-last-clock-out-time "org-clock" ())
-(declare-function org-clock-timestamps-up "org-clock" (&optional n))
-(declare-function org-clock-timestamps-down "org-clock" (&optional n))
+(declare-function org-clock-out "org-clock" (&optional switch-to-state fail-quietly at-time))
(declare-function org-clock-remove-overlays "org-clock" (&optional beg end noremove))
+(declare-function org-clock-sum "org-clock" (&optional tstart tend headline-filter propname))
(declare-function org-clock-sum-current-item "org-clock" (&optional tstart))
+(declare-function org-clock-timestamps-down "org-clock" (&optional n))
+(declare-function org-clock-timestamps-up "org-clock" (&optional n))
(declare-function org-clock-update-time-maybe "org-clock" ())
(declare-function org-clocktable-shift "org-clock" (dir n))
-
-(declare-function orgtbl-mode "org-table" (&optional arg))
-(declare-function org-clock-out "org-clock" (&optional switch-to-state fail-quietly at-time))
-(declare-function org-beamer-mode "ox-beamer" (&optional prefix) t)
-(declare-function org-table-edit-field "org-table" (arg))
-(declare-function org-table-justify-field-maybe "org-table" (&optional new))
-(declare-function org-table-set-constants "org-table" ())
-(declare-function org-table-calc-current-TBLFM "org-table" (&optional arg))
-(declare-function org-id-get-create "org-id" (&optional force))
+(declare-function org-element-at-point "org-element" ())
+(declare-function org-element-cache-refresh "org-element" (pos))
+(declare-function org-element-cache-reset "org-element" (&optional all))
+(declare-function org-element-contents "org-element" (element))
+(declare-function org-element-context "org-element" (&optional element))
+(declare-function org-element-copy "org-element" (datum))
+(declare-function org-element-interpret-data "org-element" (data))
+(declare-function org-element-lineage "org-element" (blob &optional types with-self))
+(declare-function org-element-link-parser "org-element" ())
+(declare-function org-element-nested-p "org-element" (elem-a elem-b))
+(declare-function org-element-parse-buffer "org-element" (&optional granularity visible-only))
+(declare-function org-element-property "org-element" (property element))
+(declare-function org-element-put-property "org-element" (element property value))
+(declare-function org-element-swap-A-B "org-element" (elem-a elem-b))
+(declare-function org-element-type "org-element" (element))
+(declare-function org-element-update-syntax "org-element" ())
(declare-function org-id-find-id-file "org-id" (id))
-(declare-function org-tags-view "org-agenda" (&optional todo-only match))
-(declare-function org-agenda-list "org-agenda"
- (&optional arg start-day span with-hour))
-(declare-function org-agenda-redo "org-agenda" (&optional all))
+(declare-function org-id-get-create "org-id" (&optional force))
+(declare-function org-inlinetask-at-task-p "org-inlinetask" ())
+(declare-function org-inlinetask-outline-regexp "org-inlinetask" ())
+(declare-function org-inlinetask-toggle-visibility "org-inlinetask" ())
+(declare-function org-plot/gnuplot "org-plot" (&optional params))
(declare-function org-table-align "org-table" ())
(declare-function org-table-begin "org-table" (&optional table-type))
+(declare-function org-table-beginning-of-field "org-table" (&optional n))
(declare-function org-table-blank-field "org-table" ())
+(declare-function org-table-calc-current-TBLFM "org-table" (&optional arg))
+(declare-function org-table-copy-region "org-table" (beg end &optional cut))
+(declare-function org-table-cut-region "org-table" (beg end))
+(declare-function org-table-edit-field "org-table" (arg))
(declare-function org-table-end "org-table" (&optional table-type))
+(declare-function org-table-end-of-field "org-table" (&optional n))
(declare-function org-table-insert-row "org-table" (&optional arg))
-(declare-function org-table-paste-rectangle "org-table" ())
+(declare-function org-table-justify-field-maybe "org-table" (&optional new))
(declare-function org-table-maybe-eval-formula "org-table" ())
(declare-function org-table-maybe-recalculate-line "org-table" ())
+(declare-function org-table-next-row "org-table" ())
+(declare-function org-table-paste-rectangle "org-table" ())
+(declare-function org-table-recalculate "org-table" (&optional all noalign))
+(declare-function org-table-wrap-region "org-table" (arg))
+(declare-function org-tags-view "org-agenda" (&optional todo-only match))
+(declare-function orgtbl-ascii-plot "org-table" (&optional ask))
+(declare-function orgtbl-mode "org-table" (&optional arg))
+(declare-function org-export-get-backend "ox" (name))
+(declare-function org-export-get-environment "ox" (&optional backend subtreep ext-plist))
+(declare-function org-latex-make-preamble "ox-latex" (info &optional template snippet?))
-(declare-function org-element--parse-objects "org-element"
- (beg end acc restriction))
-(declare-function org-element-at-point "org-element" (&optional keep-trail))
-(declare-function org-element-contents "org-element" (element))
-(declare-function org-element-context "org-element" (&optional element))
-(declare-function org-element-interpret-data "org-element"
- (data &optional parent))
-(declare-function org-element-map "org-element"
- (data types fun &optional
- info first-match no-recursion with-affiliated))
-(declare-function org-element-nested-p "org-element" (elem-a elem-b))
-(declare-function org-element-parse-buffer "org-element"
- (&optional granularity visible-only))
-(declare-function org-element-property "org-element" (property element))
-(declare-function org-element-put-property "org-element"
- (element property value))
-(declare-function org-element-swap-A-B "org-element" (elem-a elem-b))
-(declare-function org-element--parse-objects "org-element"
- (beg end acc restriction))
-(declare-function org-element-parse-buffer "org-element"
- (&optional granularity visible-only))
-(declare-function org-element-restriction "org-element" (element))
-(declare-function org-element-type "org-element" (element))
+(defsubst org-uniquify (list)
+ "Non-destructively remove duplicate elements from LIST."
+ (let ((res (copy-sequence list))) (delete-dups res)))
+
+(defsubst org-get-at-bol (property)
+ "Get text property PROPERTY at the beginning of line."
+ (get-text-property (point-at-bol) property))
+
+(defsubst org-trim (s &optional keep-lead)
+ "Remove whitespace at the beginning and the end of string S.
+When optional argument KEEP-LEAD is non-nil, removing blank lines
+at the beginning of the string does not affect leading indentation."
+ (replace-regexp-in-string
+ (if keep-lead "\\`\\([ \t]*\n\\)+" "\\`[ \t\n\r]+") ""
+ (replace-regexp-in-string "[ \t\n\r]+\\'" "" s)))
;; load languages based on value of `org-babel-load-languages'
(defvar org-babel-load-languages)
@@ -178,28 +199,24 @@ Stars are put in group 1 and the trimmed body in group 2.")
(defun org-babel-do-load-languages (sym value)
"Load the languages defined in `org-babel-load-languages'."
(set-default sym value)
- (mapc (lambda (pair)
- (let ((active (cdr pair)) (lang (symbol-name (car pair))))
- (if active
- (progn
- (require (intern (concat "ob-" lang))))
- (progn
- (funcall 'fmakunbound
- (intern (concat "org-babel-execute:" lang)))
- (funcall 'fmakunbound
- (intern (concat "org-babel-expand-body:" lang)))))))
- org-babel-load-languages))
+ (dolist (pair org-babel-load-languages)
+ (let ((active (cdr pair)) (lang (symbol-name (car pair))))
+ (if active
+ (require (intern (concat "ob-" lang)))
+ (funcall 'fmakunbound
+ (intern (concat "org-babel-execute:" lang)))
+ (funcall 'fmakunbound
+ (intern (concat "org-babel-expand-body:" lang)))))))
(declare-function org-babel-tangle-file "ob-tangle" (file &optional target-file lang))
;;;###autoload
(defun org-babel-load-file (file &optional compile)
- "Load Emacs Lisp source code blocks in the Org-mode FILE.
+ "Load Emacs Lisp source code blocks in the Org FILE.
This function exports the source code using `org-babel-tangle'
and then loads the resulting file using `load-file'. With prefix
arg (noninteractively: 2nd arg) COMPILE the tangled Emacs Lisp
file to byte-code before it is loaded."
(interactive "fFile to load: \nP")
- (require 'ob-core)
(let* ((age (lambda (file)
(float-time
(time-subtract (current-time)
@@ -207,11 +224,13 @@ file to byte-code before it is loaded."
(file-attributes file)))))))
(base-name (file-name-sans-extension file))
(exported-file (concat base-name ".el")))
- ;; tangle if the org-mode file is newer than the elisp file
+ ;; tangle if the Org file is newer than the elisp file
(unless (and (file-exists-p exported-file)
(> (funcall age file) (funcall age exported-file)))
+ ;; Tangle-file traversal returns reversed list of tangled files
+ ;; and we want to evaluate the first target.
(setq exported-file
- (car (org-babel-tangle-file file exported-file "emacs-lisp"))))
+ (car (last (org-babel-tangle-file file exported-file "emacs-lisp")))))
(message "%s %s"
(if compile
(progn (byte-compile-file exported-file 'load)
@@ -220,7 +239,7 @@ file to byte-code before it is loaded."
exported-file)))
(defcustom org-babel-load-languages '((emacs-lisp . t))
- "Languages which can be evaluated in Org-mode buffers.
+ "Languages which can be evaluated in Org buffers.
This list can be used to load support for any of the languages
below, note that each language will depend on a different set of
system executables and/or Emacs modes. When a language is
@@ -246,10 +265,12 @@ requirements) is loaded."
(const :tag "Ditaa" ditaa)
(const :tag "Dot" dot)
(const :tag "Emacs Lisp" emacs-lisp)
+ (const :tag "Forth" forth)
(const :tag "Fortran" fortran)
(const :tag "Gnuplot" gnuplot)
(const :tag "Haskell" haskell)
(const :tag "IO" io)
+ (const :tag "J" J)
(const :tag "Java" java)
(const :tag "Javascript" js)
(const :tag "LaTeX" latex)
@@ -272,10 +293,12 @@ requirements) is loaded."
(const :tag "Scala" scala)
(const :tag "Scheme" scheme)
(const :tag "Screen" screen)
- (const :tag "Shell Script" sh)
+ (const :tag "Shell Script" shell)
(const :tag "Shen" shen)
(const :tag "Sql" sql)
- (const :tag "Sqlite" sqlite))
+ (const :tag "Sqlite" sqlite)
+ (const :tag "Stan" stan)
+ (const :tag "ebnf2ps" ebnf2ps))
:value-type (boolean :tag "Activate" :value t)))
;;;; Customization variables
@@ -293,41 +316,318 @@ identifier."
;;;###autoload
(defun org-version (&optional here full message)
- "Show the org-mode version in the echo area.
-With prefix argument HERE, insert it at point.
-When FULL is non-nil, use a verbose version string.
-When MESSAGE is non-nil, display a message with the version."
- (interactive "P")
- (let* ((org-dir (ignore-errors (org-find-library-dir "org")))
- (save-load-suffixes (when (boundp 'load-suffixes) load-suffixes))
- (load-suffixes (list ".el"))
- (org-install-dir (ignore-errors (org-find-library-dir "org-loaddefs")))
- (org-trash (or
- (and (fboundp 'org-release) (fboundp 'org-git-version))
- (org-load-noerror-mustsuffix (concat org-dir "org-version"))))
- (load-suffixes save-load-suffixes)
- (org-version (org-release))
- (git-version (org-git-version))
- (version (format "Org-mode version %s (%s @ %s)"
- org-version
- git-version
- (if org-install-dir
- (if (string= org-dir org-install-dir)
- org-install-dir
- (concat "mixed installation! " org-install-dir " and " org-dir))
- "org-loaddefs.el can not be found!")))
- (version1 (if full version org-version)))
- (if (org-called-interactively-p 'interactive)
- (if here
- (insert version)
- (message version))
- (if message (message version1))
+ "Show the Org version.
+Interactively, or when MESSAGE is non-nil, show it in echo area.
+With prefix argument, or when HERE is non-nil, insert it at point.
+In non-interactive uses, a reduced version string is output unless
+FULL is given."
+ (interactive (list current-prefix-arg t (not current-prefix-arg)))
+ (let ((org-dir (ignore-errors (org-find-library-dir "org")))
+ (save-load-suffixes (when (boundp 'load-suffixes) load-suffixes))
+ (load-suffixes (list ".el"))
+ (org-install-dir
+ (ignore-errors (org-find-library-dir "org-loaddefs"))))
+ (unless (and (fboundp 'org-release) (fboundp 'org-git-version))
+ (org-load-noerror-mustsuffix (concat org-dir "org-version")))
+ (let* ((load-suffixes save-load-suffixes)
+ (release (org-release))
+ (git-version (org-git-version))
+ (version (format "Org mode version %s (%s @ %s)"
+ release
+ git-version
+ (if org-install-dir
+ (if (string= org-dir org-install-dir)
+ org-install-dir
+ (concat "mixed installation! "
+ org-install-dir
+ " and "
+ org-dir))
+ "org-loaddefs.el can not be found!")))
+ (version1 (if full version release)))
+ (when here (insert version1))
+ (when message (message "%s" version1))
version1)))
(defconst org-version (org-version))
-;;; Compatibility constants
+
+;;; Syntax Constants
+
+;;;; Block
+
+(defconst org-block-regexp
+ "^[ \t]*#\\+begin_?\\([^ \n]+\\)\\(\\([^\n]+\\)\\)?\n\\([^\000]+?\\)#\\+end_?\\1[ \t]*$"
+ "Regular expression for hiding blocks.")
+
+(defconst org-dblock-start-re
+ "^[ \t]*#\\+\\(?:BEGIN\\|begin\\):[ \t]+\\(\\S-+\\)\\([ \t]+\\(.*\\)\\)?"
+ "Matches the start line of a dynamic block, with parameters.")
+
+(defconst org-dblock-end-re "^[ \t]*#\\+\\(?:END\\|end\\)\\([: \t\r\n]\\|$\\)"
+ "Matches the end of a dynamic block.")
+
+;;;; Clock and Planning
+
+(defconst org-clock-string "CLOCK:"
+ "String used as prefix for timestamps clocking work hours on an item.")
+
+(defvar org-closed-string "CLOSED:"
+ "String used as the prefix for timestamps logging closing a TODO entry.")
+
+(defvar org-deadline-string "DEADLINE:"
+ "String to mark deadline entries.
+\\
+A deadline is this string, followed by a time stamp. It must be
+a word, terminated by a colon. You can insert a schedule keyword
+and a timestamp with `\\[org-deadline]'.")
+
+(defvar org-scheduled-string "SCHEDULED:"
+ "String to mark scheduled TODO entries.
+\\
+A schedule is this string, followed by a time stamp. It must be
+a word, terminated by a colon. You can insert a schedule keyword
+and a timestamp with `\\[org-schedule]'.")
+
+(defconst org-ds-keyword-length
+ (+ 2
+ (apply #'max
+ (mapcar #'length
+ (list org-deadline-string org-scheduled-string
+ org-clock-string org-closed-string))))
+ "Maximum length of the DEADLINE and SCHEDULED keywords.")
+
+(defconst org-planning-line-re
+ (concat "^[ \t]*"
+ (regexp-opt
+ (list org-closed-string org-deadline-string org-scheduled-string)
+ t))
+ "Matches a line with planning info.
+Matched keyword is in group 1.")
+
+(defconst org-clock-line-re
+ (concat "^[ \t]*" org-clock-string)
+ "Matches a line with clock info.")
+
+(defconst org-deadline-regexp (concat "\\<" org-deadline-string)
+ "Matches the DEADLINE keyword.")
+
+(defconst org-deadline-time-regexp
+ (concat "\\<" org-deadline-string " *<\\([^>]+\\)>")
+ "Matches the DEADLINE keyword together with a time stamp.")
+
+(defconst org-deadline-time-hour-regexp
+ (concat "\\<" org-deadline-string
+ " *<\\([^>]+[0-9]\\{1,2\\}:[0-9]\\{2\\}[0-9-+:hdwmy \t.]*\\)>")
+ "Matches the DEADLINE keyword together with a time-and-hour stamp.")
+
+(defconst org-deadline-line-regexp
+ (concat "\\<\\(" org-deadline-string "\\).*")
+ "Matches the DEADLINE keyword and the rest of the line.")
+
+(defconst org-scheduled-regexp (concat "\\<" org-scheduled-string)
+ "Matches the SCHEDULED keyword.")
+
+(defconst org-scheduled-time-regexp
+ (concat "\\<" org-scheduled-string " *<\\([^>]+\\)>")
+ "Matches the SCHEDULED keyword together with a time stamp.")
+
+(defconst org-scheduled-time-hour-regexp
+ (concat "\\<" org-scheduled-string
+ " *<\\([^>]+[0-9]\\{1,2\\}:[0-9]\\{2\\}[0-9-+:hdwmy \t.]*\\)>")
+ "Matches the SCHEDULED keyword together with a time-and-hour stamp.")
+
+(defconst org-closed-time-regexp
+ (concat "\\<" org-closed-string " *\\[\\([^]]+\\)\\]")
+ "Matches the CLOSED keyword together with a time stamp.")
+
+(defconst org-keyword-time-regexp
+ (concat "\\<"
+ (regexp-opt
+ (list org-scheduled-string org-deadline-string org-closed-string
+ org-clock-string)
+ t)
+ " *[[<]\\([^]>]+\\)[]>]")
+ "Matches any of the 4 keywords, together with the time stamp.")
+
+(defconst org-keyword-time-not-clock-regexp
+ (concat
+ "\\<"
+ (regexp-opt
+ (list org-scheduled-string org-deadline-string org-closed-string) t)
+ " *[[<]\\([^]>]+\\)[]>]")
+ "Matches any of the 3 keywords, together with the time stamp.")
+
+(defconst org-maybe-keyword-time-regexp
+ (concat "\\(\\<"
+ (regexp-opt
+ (list org-scheduled-string org-deadline-string org-closed-string
+ org-clock-string)
+ t)
+ "\\)?"
+ " *\\([[<][0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} ?[^]\r\n>]*?[]>]"
+ "\\|"
+ "<%%([^\r\n>]*>\\)")
+ "Matches a timestamp, possibly preceded by a keyword.")
+
+(defconst org-all-time-keywords
+ (mapcar (lambda (w) (substring w 0 -1))
+ (list org-scheduled-string org-deadline-string
+ org-clock-string org-closed-string))
+ "List of time keywords.")
+
+;;;; Drawer
+
+(defconst org-drawer-regexp "^[ \t]*:\\(\\(?:\\w\\|[-_]\\)+\\):[ \t]*$"
+ "Matches first or last line of a hidden block.
+Group 1 contains drawer's name or \"END\".")
+
+(defconst org-property-start-re "^[ \t]*:PROPERTIES:[ \t]*$"
+ "Regular expression matching the first line of a property drawer.")
+
+(defconst org-property-end-re "^[ \t]*:END:[ \t]*$"
+ "Regular expression matching the last line of a property drawer.")
+
+(defconst org-clock-drawer-start-re "^[ \t]*:CLOCK:[ \t]*$"
+ "Regular expression matching the first line of a clock drawer.")
+
+(defconst org-clock-drawer-end-re "^[ \t]*:END:[ \t]*$"
+ "Regular expression matching the last line of a clock drawer.")
+
+(defconst org-property-drawer-re
+ (concat "^[ \t]*:PROPERTIES:[ \t]*\n"
+ "\\(?:[ \t]*:\\S-+:\\(?: .*\\)?[ \t]*\n\\)*?"
+ "[ \t]*:END:[ \t]*$")
+ "Matches an entire property drawer.")
+
+(defconst org-clock-drawer-re
+ (concat "\\(" org-clock-drawer-start-re "\\)[^\000]*?\\("
+ org-clock-drawer-end-re "\\)\n?")
+ "Matches an entire clock drawer.")
+
+;;;; Headline
+
+(defconst org-heading-keyword-regexp-format
+ "^\\(\\*+\\)\\(?: +%s\\)\\(?: +\\(.*?\\)\\)?[ \t]*$"
+ "Printf format for a regexp matching a headline with some keyword.
+This regexp will match the headline of any node which has the
+exact keyword that is put into the format. The keyword isn't in
+any group by default, but the stars and the body are.")
+
+(defconst org-heading-keyword-maybe-regexp-format
+ "^\\(\\*+\\)\\(?: +%s\\)?\\(?: +\\(.*?\\)\\)?[ \t]*$"
+ "Printf format for a regexp matching a headline, possibly with some keyword.
+This regexp can match any headline with the specified keyword, or
+without a keyword. The keyword isn't in any group by default,
+but the stars and the body are.")
+
+(defconst org-archive-tag "ARCHIVE"
+ "The tag that marks a subtree as archived.
+An archived subtree does not open during visibility cycling, and does
+not contribute to the agenda listings.")
+
+(defconst org-comment-string "COMMENT"
+ "Entries starting with this keyword will never be exported.
+\\
+An entry can be toggled between COMMENT and normal with
+`\\[org-toggle-comment]'.")
+
+
+;;;; LaTeX Environments and Fragments
+
+(defconst org-latex-regexps
+ '(("begin" "^[ \t]*\\(\\\\begin{\\([a-zA-Z0-9\\*]+\\)[^\000]+?\\\\end{\\2}\\)" 1 t)
+ ;; ("$" "\\([ \t(]\\|^\\)\\(\\(\\([$]\\)\\([^ \t\n,.$].*?\\(\n.*?\\)\\{0,5\\}[^ \t\n,.$]\\)\\4\\)\\)\\([ \t.,?;:'\")]\\|$\\)" 2 nil)
+ ;; \000 in the following regex is needed for org-inside-LaTeX-fragment-p
+ ("$1" "\\([^$]\\|^\\)\\(\\$[^ \t\r\n,;.$]\\$\\)\\(\\s.\\|\\s-\\|\\s(\\|\\s)\\|\\s\"\\|\000\\|'\\|$\\)" 2 nil)
+ ("$" "\\([^$]\\|^\\)\\(\\(\\$\\([^ \t\n,;.$][^$\n\r]*?\\(\n[^$\n\r]*?\\)\\{0,2\\}[^ \t\n,.$]\\)\\$\\)\\)\\(\\s.\\|\\s-\\|\\s(\\|\\s)\\|\\s\"\\|\000\\|'\\|$\\)" 2 nil)
+ ("\\(" "\\\\([^\000]*?\\\\)" 0 nil)
+ ("\\[" "\\\\\\[[^\000]*?\\\\\\]" 0 nil)
+ ("$$" "\\$\\$[^\000]*?\\$\\$" 0 nil))
+ "Regular expressions for matching embedded LaTeX.")
+
+;;;; Node Property
+
+(defconst org-effort-property "Effort"
+ "The property that is being used to keep track of effort estimates.
+Effort estimates given in this property need to have the format H:MM.")
+
+;;;; Table
+
+(defconst org-table-any-line-regexp "^[ \t]*\\(|\\|\\+-[-+]\\)"
+ "Detect an org-type or table-type table.")
+
+(defconst org-table-line-regexp "^[ \t]*|"
+ "Detect an org-type table line.")
+
+(defconst org-table-dataline-regexp "^[ \t]*|[^-]"
+ "Detect an org-type table line.")
+
+(defconst org-table-hline-regexp "^[ \t]*|-"
+ "Detect an org-type table hline.")
+
+(defconst org-table1-hline-regexp "^[ \t]*\\+-[-+]"
+ "Detect a table-type table hline.")
+
+(defconst org-table-any-border-regexp "^[ \t]*[^|+ \t]"
+ "Detect the first line outside a table when searching from within it.
+This works for both table types.")
+
+(defconst org-TBLFM-regexp "^[ \t]*#\\+TBLFM: "
+ "Detect a #+TBLFM line.")
+
+;;;; Timestamp
+
+(defconst org-ts-regexp "<\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} ?[^\r\n>]*?\\)>"
+ "Regular expression for fast time stamp matching.")
+
+(defconst org-ts-regexp-inactive
+ "\\[\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} ?[^\r\n>]*?\\)\\]"
+ "Regular expression for fast inactive time stamp matching.")
+
+(defconst org-ts-regexp-both "[[<]\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} ?[^]\r\n>]*?\\)[]>]"
+ "Regular expression for fast time stamp matching.")
+
+(defconst org-ts-regexp0
+ "\\(\\([0-9]\\{4\\}\\)-\\([0-9]\\{2\\}\\)-\\([0-9]\\{2\\}\\)\\( +[^]+0-9>\r\n -]+\\)?\\( +\\([0-9]\\{1,2\\}\\):\\([0-9]\\{2\\}\\)\\)?\\)"
+ "Regular expression matching time strings for analysis.
+This one does not require the space after the date, so it can be used
+on a string that terminates immediately after the date.")
+
+(defconst org-ts-regexp1 "\\(\\([0-9]\\{4\\}\\)-\\([0-9]\\{2\\}\\)-\\([0-9]\\{2\\}\\) *\\([^]+0-9>\r\n -]*\\)\\( \\([0-9]\\{1,2\\}\\):\\([0-9]\\{2\\}\\)\\)?\\)"
+ "Regular expression matching time strings for analysis.")
+
+(defconst org-ts-regexp2 (concat "<" org-ts-regexp1 "[^>\n]\\{0,16\\}>")
+ "Regular expression matching time stamps, with groups.")
+
+(defconst org-ts-regexp3 (concat "[[<]" org-ts-regexp1 "[^]>\n]\\{0,16\\}[]>]")
+ "Regular expression matching time stamps (also [..]), with groups.")
+
+(defconst org-tr-regexp (concat org-ts-regexp "--?-?" org-ts-regexp)
+ "Regular expression matching a time stamp range.")
+
+(defconst org-tr-regexp-both
+ (concat org-ts-regexp-both "--?-?" org-ts-regexp-both)
+ "Regular expression matching a time stamp range.")
+
+(defconst org-tsr-regexp (concat org-ts-regexp "\\(--?-?"
+ org-ts-regexp "\\)?")
+ "Regular expression matching a time stamp or time stamp range.")
+
+(defconst org-tsr-regexp-both
+ (concat org-ts-regexp-both "\\(--?-?"
+ org-ts-regexp-both "\\)?")
+ "Regular expression matching a time stamp or time stamp range.
+The time stamps may be either active or inactive.")
+(defconst org-repeat-re
+ "<[0-9]\\{4\\}-[0-9][0-9]-[0-9][0-9] [^>\n]*?\\([.+]?\\+[0-9]+[hdwmy]\\(/[0-9]+[hdwmy]\\)?\\)"
+ "Regular expression for specifying repeated events.
+After a match, group 1 contains the repeat expression.")
+
+(defconst org-time-stamp-formats '("<%Y-%m-%d %a>" . "<%Y-%m-%d %a %H:%M>")
+ "Formats for `format-time-string' which are used for time stamps.")
+
+
;;; The custom variables
(defgroup org nil
@@ -337,7 +637,7 @@ When MESSAGE is non-nil, display a message with the version."
:group 'calendar)
(defcustom org-mode-hook nil
- "Mode hook for Org-mode, run after the mode was turned on."
+ "Mode hook for Org mode, run after the mode was turned on."
:group 'org
:type 'hook)
@@ -359,17 +659,17 @@ When MESSAGE is non-nil, display a message with the version."
(defun org-load-modules-maybe (&optional force)
"Load all extensions listed in `org-modules'."
(when (or force (not org-modules-loaded))
- (mapc (lambda (ext)
- (condition-case nil (require ext)
- (error (message "Problems while trying to load feature `%s'" ext))))
- org-modules)
+ (dolist (ext org-modules)
+ (condition-case nil (require ext)
+ (error (message "Problems while trying to load feature `%s'" ext))))
(setq org-modules-loaded t)))
(defun org-set-modules (var value)
"Set VAR to VALUE and call `org-load-modules-maybe' with the force flag."
(set var value)
(when (featurep 'org)
- (org-load-modules-maybe 'force)))
+ (org-load-modules-maybe 'force)
+ (org-element-cache-reset 'all)))
(defcustom org-modules '(org-w3m org-bbdb org-bibtex org-docview org-gnus org-info org-irc org-mhe org-rmail)
"Modules that should always be loaded together with org.el.
@@ -397,6 +697,7 @@ For export specific modules, see also `org-export-backends'."
(const :tag " crypt: Encryption of subtrees" org-crypt)
(const :tag " ctags: Access to Emacs tags with links" org-ctags)
(const :tag " docview: Links to doc-view buffers" org-docview)
+ (const :tag " eww: Store link to url of eww" org-eww)
(const :tag " gnus: Links to GNUS folders/messages" org-gnus)
(const :tag " habit: Track your consistency with habits" org-habit)
(const :tag " id: Global IDs for identifying entries" org-id)
@@ -407,52 +708,50 @@ For export specific modules, see also `org-export-backends'."
(const :tag " mouse: Additional mouse support" org-mouse)
(const :tag " protocol: Intercept calls from emacsclient" org-protocol)
(const :tag " rmail: Links to RMAIL folders/messages" org-rmail)
- (const :tag " w3m: Special cut/paste from w3m to Org-mode." org-w3m)
+ (const :tag " w3m: Special cut/paste from w3m to Org mode." org-w3m)
(const :tag "C annotate-file: Annotate a file with org syntax" org-annotate-file)
- (const :tag "C bookmark: Org-mode links to bookmarks" org-bookmark)
+ (const :tag "C bookmark: Org links to bookmarks" org-bookmark)
(const :tag "C bullets: Add overlays to headlines stars" org-bullets)
(const :tag "C checklist: Extra functions for checklists in repeated tasks" org-checklist)
(const :tag "C choose: Use TODO keywords to mark decisions states" org-choose)
(const :tag "C collector: Collect properties into tables" org-collector)
- (const :tag "C depend: TODO dependencies for Org-mode\n\t\t\t(PARTIALLY OBSOLETE, see built-in dependency support))" org-depend)
- (const :tag "C drill: Flashcards and spaced repetition for Org-mode" org-drill)
- (const :tag "C elisp-symbol: Org-mode links to emacs-lisp symbols" org-elisp-symbol)
+ (const :tag "C depend: TODO dependencies for Org mode\n\t\t\t(PARTIALLY OBSOLETE, see built-in dependency support))" org-depend)
+ (const :tag "C drill: Flashcards and spaced repetition for Org mode" org-drill)
+ (const :tag "C elisp-symbol: Org links to emacs-lisp symbols" org-elisp-symbol)
(const :tag "C eshell Support for links to working directories in eshell" org-eshell)
(const :tag "C eval-light: Evaluate inbuffer-code on demand" org-eval-light)
(const :tag "C eval: Include command output as text" org-eval)
- (const :tag "C expiry: Expiry mechanism for Org-mode entries" org-expiry)
+ (const :tag "C expiry: Expiry mechanism for Org entries" org-expiry)
(const :tag "C favtable: Lookup table of favorite references and links" org-favtable)
(const :tag "C git-link: Provide org links to specific file version" org-git-link)
(const :tag "C interactive-query: Interactive modification of tags query\n\t\t\t(PARTIALLY OBSOLETE, see secondary filtering)" org-interactive-query)
- (const :tag "C invoice: Help manage client invoices in Org-mode" org-invoice)
- (const :tag "C jira: Add a jira:ticket protocol to Org-mode" org-jira)
+ (const :tag "C invoice: Help manage client invoices in Org mode" org-invoice)
(const :tag "C learn: SuperMemo's incremental learning algorithm" org-learn)
(const :tag "C mac-iCal Imports events from iCal.app to the Emacs diary" org-mac-iCal)
(const :tag "C mac-link: Grab links and url from various mac Applications" org-mac-link)
- (const :tag "C mairix: Hook mairix search into Org-mode for different MUAs" org-mairix)
- (const :tag "C man: Support for links to manpages in Org-mode" org-man)
+ (const :tag "C mairix: Hook mairix search into Org for different MUAs" org-mairix)
+ (const :tag "C man: Support for links to manpages in Org mode" org-man)
(const :tag "C mew: Links to Mew folders/messages" org-mew)
(const :tag "C mtags: Support for muse-like tags" org-mtags)
(const :tag "C notmuch: Provide org links to notmuch searches or messages" org-notmuch)
(const :tag "C panel: Simple routines for us with bad memory" org-panel)
- (const :tag "C registry: A registry for Org-mode links" org-registry)
- (const :tag "C screen: Visit screen sessions through Org-mode links" org-screen)
+ (const :tag "C registry: A registry for Org links" org-registry)
+ (const :tag "C screen: Visit screen sessions through Org links" org-screen)
(const :tag "C secretary: Team management with org-mode" org-secretary)
- (const :tag "C sqlinsert: Convert Org-mode tables to SQL insertions" orgtbl-sqlinsert)
- (const :tag "C toc: Table of contents for Org-mode buffer" org-toc)
- (const :tag "C track: Keep up with Org-mode development" org-track)
+ (const :tag "C sqlinsert: Convert Org tables to SQL insertions" orgtbl-sqlinsert)
+ (const :tag "C toc: Table of contents for Org buffer" org-toc)
+ (const :tag "C track: Keep up with Org mode development" org-track)
(const :tag "C velocity Something like Notational Velocity for Org" org-velocity)
(const :tag "C vm: Links to VM folders/messages" org-vm)
(const :tag "C wikinodes: CamelCase wiki-like links" org-wikinodes)
(const :tag "C wl: Links to Wanderlust folders/messages" org-wl)
(repeat :tag "External packages" :inline t (symbol :tag "Package"))))
-(defvar org-export--registered-backends) ; From ox.el.
+(defvar org-export-registered-backends) ; From ox.el.
(declare-function org-export-derived-backend-p "ox" (backend &rest backends))
(declare-function org-export-backend-name "ox" (backend) t)
-(declare-function org-export-backend-options "ox" (cl-x) t)
-(defcustom org-export-backends '(ascii html icalendar latex)
+(defcustom org-export-backends '(ascii html icalendar latex odt)
"List of export back-ends that should be always available.
If a description starts with , the file is not part of Emacs
@@ -469,8 +768,8 @@ interface or run the following code, where VAL stands for the new
value of the variable, after updating it:
(progn
- (setq org-export--registered-backends
- (org-remove-if-not
+ (setq org-export-registered-backends
+ (cl-remove-if-not
(lambda (backend)
(let ((name (org-export-backend-name backend)))
(or (memq name val)
@@ -478,9 +777,9 @@ value of the variable, after updating it:
(dolist (b val)
(and (org-export-derived-backend-p b name)
(throw \\='parentp t)))))))
- org-export--registered-backends))
- (let ((new-list (mapcar \\='org-export-backend-name
- org-export--registered-backends)))
+ org-export-registered-backends))
+ (let ((new-list (mapcar #\\='org-export-backend-name
+ org-export-registered-backends)))
(dolist (backend val)
(cond
((not (load (format \"ox-%s\" backend) t t))
@@ -493,16 +792,16 @@ Adding a back-end to this list will also pull the back-end it
depends on, if any."
:group 'org
:group 'org-export
- :version "24.4"
- :package-version '(Org . "8.0")
+ :version "26.1"
+ :package-version '(Org . "9.0")
:initialize 'custom-initialize-set
:set (lambda (var val)
(if (not (featurep 'ox)) (set-default var val)
;; Any back-end not required anymore (not present in VAL and not
;; a parent of any back-end in the new value) is removed from the
;; list of registered back-ends.
- (setq org-export--registered-backends
- (org-remove-if-not
+ (setq org-export-registered-backends
+ (cl-remove-if-not
(lambda (backend)
(let ((name (org-export-backend-name backend)))
(or (memq name val)
@@ -510,11 +809,11 @@ depends on, if any."
(dolist (b val)
(and (org-export-derived-backend-p b name)
(throw 'parentp t)))))))
- org-export--registered-backends))
+ org-export-registered-backends))
;; Now build NEW-LIST of both new back-ends and required
;; parents.
- (let ((new-list (mapcar 'org-export-backend-name
- org-export--registered-backends)))
+ (let ((new-list (mapcar #'org-export-backend-name
+ org-export-registered-backends)))
(dolist (backend val)
(cond
((not (load (format "ox-%s" backend) t t))
@@ -544,19 +843,18 @@ depends on, if any."
(const :tag "C taskjuggler Export buffer to TaskJuggler format" taskjuggler)))
(eval-after-load 'ox
- '(mapc
- (lambda (backend)
- (condition-case nil (require (intern (format "ox-%s" backend)))
- (error (message "Problems while trying to load export back-end `%s'"
- backend))))
- org-export-backends))
+ '(dolist (backend org-export-backends)
+ (condition-case nil (require (intern (format "ox-%s" backend)))
+ (error (message "Problems while trying to load export back-end `%s'"
+ backend)))))
(defcustom org-support-shift-select nil
"Non-nil means make shift-cursor commands select text when possible.
+\\\
In Emacs 23, when `shift-select-mode' is on, shifted cursor keys
start selecting a region, or enlarge regions started in this way.
-In Org-mode, in special contexts, these same keys are used for
+In Org mode, in special contexts, these same keys are used for
other purposes, important enough to compete with shift selection.
Org tries to balance these needs by supporting `shift-select-mode'
outside these special contexts, under control of this variable.
@@ -571,7 +869,7 @@ cursor keys will then execute Org commands in the following contexts:
Outside these contexts, the commands will throw an error.
When this variable is t and the cursor is not in a special
-context, Org-mode will support shift-selection for making and
+context, Org mode will support shift-selection for making and
enlarging regions. To make this more effective, the bullet
cycling will no longer happen anywhere in an item line, but only
if the cursor is exactly on the bullet.
@@ -579,16 +877,16 @@ if the cursor is exactly on the bullet.
If you set this variable to the symbol `always', then the keys
will not be special in headlines, property lines, and item lines,
to make shift selection work there as well. If this is what you
-want, you can use the following alternative commands: `C-c C-t'
-and `C-c ,' to change TODO state and priority, `C-u C-u C-c C-t'
-can be used to switch TODO sets, `C-c -' to cycle item bullet
-types, and properties can be edited by hand or in column view.
+want, you can use the following alternative commands:
+`\\[org-todo]' and `\\[org-priority]' \
+to change TODO state and priority,
+`\\[universal-argument] \\[universal-argument] \\[org-todo]' \
+can be used to switch TODO sets,
+`\\[org-ctrl-c-minus]' to cycle item bullet types,
+and properties can be edited by hand or in column view.
However, when the cursor is on a timestamp, shift-cursor commands
-will still edit the time stamp - this is just too good to give up.
-
-XEmacs user should have this variable set to nil, because
-`shift-select-mode' is in Emacs 23 or later only."
+will still edit the time stamp - this is just too good to give up."
:group 'org
:type '(choice
(const :tag "Never" nil)
@@ -622,12 +920,13 @@ already archived entries."
:group 'org-archive)
(defgroup org-startup nil
- "Options concerning startup of Org-mode."
+ "Options concerning startup of Org mode."
:tag "Org Startup"
:group 'org)
(defcustom org-startup-folded t
- "Non-nil means entering Org-mode will switch to OVERVIEW.
+ "Non-nil means entering Org mode will switch to OVERVIEW.
+
This can also be configured on a per-file basis by adding one of
the following lines anywhere in the buffer:
@@ -636,9 +935,9 @@ the following lines anywhere in the buffer:
#+STARTUP: content
#+STARTUP: showeverything
-By default, this option is ignored when Org opens agenda files
-for the first time. If you want the agenda to honor the startup
-option, set `org-agenda-inhibit-startup' to nil."
+Set `org-agenda-inhibit-startup' to a non-nil value if you want
+to ignore this option when Org opens agenda files for the first
+time."
:group 'org-startup
:type '(choice
(const :tag "nofold: show all" nil)
@@ -647,9 +946,18 @@ option, set `org-agenda-inhibit-startup' to nil."
(const :tag "show everything, even drawers" showeverything)))
(defcustom org-startup-truncated t
- "Non-nil means entering Org-mode will set `truncate-lines'.
+ "Non-nil means entering Org mode will set `truncate-lines'.
This is useful since some lines containing links can be very long and
-uninteresting. Also tables look terrible when wrapped."
+uninteresting. Also tables look terrible when wrapped.
+
+The variable `org-startup-truncated' allows to configure
+truncation for Org mode different to the other modes that use the
+variable `truncate-lines' and as a shortcut instead of putting
+the variable `truncate-lines' into the `org-mode-hook'. If one
+wants to configure truncation for Org mode not statically but
+dynamically e. g. in a hook like `ediff-prepare-buffer-hook' then
+the variable `truncate-lines' has to be used because in such a
+case it is too late to set the variable `org-startup-truncated'."
:group 'org-startup
:type 'boolean)
@@ -742,26 +1050,26 @@ the following lines anywhere in the buffer:
:type 'boolean)
(defcustom org-insert-mode-line-in-empty-file nil
- "Non-nil means insert the first line setting Org-mode in empty files.
+ "Non-nil means insert the first line setting Org mode in empty files.
When the function `org-mode' is called interactively in an empty file, this
-normally means that the file name does not automatically trigger Org-mode.
-To ensure that the file will always be in Org-mode in the future, a
-line enforcing Org-mode will be inserted into the buffer, if this option
+normally means that the file name does not automatically trigger Org mode.
+To ensure that the file will always be in Org mode in the future, a
+line enforcing Org mode will be inserted into the buffer, if this option
has been set."
:group 'org-startup
:type 'boolean)
(defcustom org-replace-disputed-keys nil
"Non-nil means use alternative key bindings for some keys.
-Org-mode uses S- keys for changing timestamps and priorities.
+Org mode uses S- keys for changing timestamps and priorities.
These keys are also used by other packages like shift-selection-mode'
\(built into Emacs 23), `CUA-mode' or `windmove.el'.
-If you want to use Org-mode together with one of these other modes,
-or more generally if you would like to move some Org-mode commands to
+If you want to use Org mode together with one of these other modes,
+or more generally if you would like to move some Org mode commands to
other keys, set this variable and configure the keys with the variable
`org-disputed-keys'.
-This option is only relevant at load-time of Org-mode, and must be set
+This option is only relevant at load-time of Org mode, and must be set
*before* org.el is loaded. Changing it requires a restart of Emacs to
become effective."
:group 'org-startup
@@ -769,18 +1077,13 @@ become effective."
(defcustom org-use-extra-keys nil
"Non-nil means use extra key sequence definitions for certain commands.
-This happens automatically if you run XEmacs or if `window-system'
-is nil. This variable lets you do the same manually. You must
-set it before loading org.
-
-Example: on Carbon Emacs 22 running graphically, with an external
-keyboard on a Powerbook, the default way of setting M-left might
-not work for either Alt or ESC. Setting this variable will make
-it work for ESC."
+This happens automatically if `window-system' is nil. This
+variable lets you do the same manually. You must set it before
+loading Org."
:group 'org-startup
:type 'boolean)
-(org-defvaralias 'org-CUA-compatible 'org-replace-disputed-keys)
+(defvaralias 'org-CUA-compatible 'org-replace-disputed-keys)
(defcustom org-disputed-keys
'(([(shift up)] . [(meta p)])
@@ -789,90 +1092,52 @@ it work for ESC."
([(shift right)] . [(meta +)])
([(control shift right)] . [(meta shift +)])
([(control shift left)] . [(meta shift -)]))
- "Keys for which Org-mode and other modes compete.
+ "Keys for which Org mode and other modes compete.
This is an alist, cars are the default keys, second element specifies
the alternative to use when `org-replace-disputed-keys' is t.
Keys can be specified in any syntax supported by `define-key'.
-The value of this option takes effect only at Org-mode's startup,
+The value of this option takes effect only at Org mode startup,
therefore you'll have to restart Emacs to apply it after changing."
:group 'org-startup
:type 'alist)
(defun org-key (key)
"Select key according to `org-replace-disputed-keys' and `org-disputed-keys'.
-Or return the original if not disputed.
-Also apply the translations defined in `org-xemacs-key-equivalents'."
+Or return the original if not disputed."
(when org-replace-disputed-keys
(let* ((nkey (key-description key))
- (x (org-find-if (lambda (x)
- (equal (key-description (car x)) nkey))
- org-disputed-keys)))
+ (x (cl-find-if (lambda (x) (equal (key-description (car x)) nkey))
+ org-disputed-keys)))
(setq key (if x (cdr x) key))))
- (when (featurep 'xemacs)
- (setq key (or (cdr (assoc key org-xemacs-key-equivalents)) key)))
key)
-(defun org-find-if (predicate seq)
- (catch 'exit
- (while seq
- (if (funcall predicate (car seq))
- (throw 'exit (car seq))
- (pop seq)))))
-
(defun org-defkey (keymap key def)
"Define a key, possibly translated, as returned by `org-key'."
(define-key keymap (org-key key) def))
(defcustom org-ellipsis nil
- "The ellipsis to use in the Org-mode outline.
+ "The ellipsis to use in the Org mode outline.
+
When nil, just use the standard three dots.
When a string, use that string instead.
-When a face, use the standard 3 dots, but with the specified face.
-The change affects only Org-mode (which will then use its own display table).
+
+The change affects only Org mode (which will then use its own display table).
Changing this requires executing `\\[org-mode]' in a buffer to become
effective."
:group 'org-startup
:type '(choice (const :tag "Default" nil)
- (face :tag "Face" :value org-warning)
- (string :tag "String" :value "...#")))
+ (string :tag "String" :value "...#"))
+ :safe #'string-or-null-p)
(defvar org-display-table nil
"The display table for org-mode, in case `org-ellipsis' is non-nil.")
(defgroup org-keywords nil
- "Keywords in Org-mode."
+ "Keywords in Org mode."
:tag "Org Keywords"
:group 'org)
-(defcustom org-deadline-string "DEADLINE:"
- "String to mark deadline entries.
-A deadline is this string, followed by a time stamp. Should be a word,
-terminated by a colon. You can insert a schedule keyword and
-a timestamp with \\[org-deadline].
-Changes become only effective after restarting Emacs."
- :group 'org-keywords
- :type 'string)
-
-(defcustom org-scheduled-string "SCHEDULED:"
- "String to mark scheduled TODO entries.
-A schedule is this string, followed by a time stamp. Should be a word,
-terminated by a colon. You can insert a schedule keyword and
-a timestamp with \\[org-schedule].
-Changes become only effective after restarting Emacs."
- :group 'org-keywords
- :type 'string)
-
-(defcustom org-closed-string "CLOSED:"
- "String used as the prefix for timestamps logging closing a TODO entry."
- :group 'org-keywords
- :type 'string)
-
-(defcustom org-clock-string "CLOCK:"
- "String used as prefix for timestamps clocking work hours on an item."
- :group 'org-keywords
- :type 'string)
-
(defcustom org-closed-keep-when-no-todo nil
"Remove CLOSED: time-stamp when switching back to a non-todo state?"
:group 'org-todo
@@ -881,37 +1146,8 @@ Changes become only effective after restarting Emacs."
:package-version '(Org . "8.0")
:type 'boolean)
-(defconst org-planning-or-clock-line-re (concat "^[ \t]*\\("
- org-scheduled-string "\\|"
- org-deadline-string "\\|"
- org-closed-string "\\|"
- org-clock-string "\\)")
- "Matches a line with planning or clock info.")
-
-(defcustom org-comment-string "COMMENT"
- "Entries starting with this keyword will never be exported.
-An entry can be toggled between COMMENT and normal with
-\\[org-toggle-comment].
-Changes become only effective after restarting Emacs."
- :group 'org-keywords
- :type 'string)
-
-(defcustom org-quote-string "QUOTE"
- "Entries starting with this keyword will be exported in fixed-width font.
-Quoting applies only to the text in the entry following the headline, and does
-not extend beyond the next headline, even if that is lower level.
-An entry can be toggled between QUOTE and normal with
-\\[org-toggle-fixed-width-section]."
- :group 'org-keywords
- :type 'string)
-
-(defconst org-repeat-re
- "<[0-9]\\{4\\}-[0-9][0-9]-[0-9][0-9] [^>\n]*?\\([.+]?\\+[0-9]+[hdwmy]\\(/[0-9]+[hdwmy]\\)?\\)"
- "Regular expression for specifying repeated events.
-After a match, group 1 contains the repeat expression.")
-
(defgroup org-structure nil
- "Options concerning the general structure of Org-mode files."
+ "Options concerning the general structure of Org files."
:tag "Org Structure"
:group 'org)
@@ -920,92 +1156,88 @@ After a match, group 1 contains the repeat expression.")
:tag "Org Reveal Location"
:group 'org-structure)
-(defconst org-context-choice
- '(choice
- (const :tag "Always" t)
- (const :tag "Never" nil)
- (repeat :greedy t :tag "Individual contexts"
- (cons
- (choice :tag "Context"
- (const agenda)
- (const org-goto)
- (const occur-tree)
- (const tags-tree)
- (const link-search)
- (const mark-goto)
- (const bookmark-jump)
- (const isearch)
- (const default))
- (boolean))))
- "Contexts for the reveal options.")
-
-(defcustom org-show-hierarchy-above '((default . t))
- "Non-nil means show full hierarchy when revealing a location.
-Org-mode often shows locations in an org-mode file which might have
-been invisible before. When this is set, the hierarchy of headings
-above the exposed location is shown.
-Turning this off for example for sparse trees makes them very compact.
-Instead of t, this can also be an alist specifying this option for different
-contexts. Valid contexts are
+(defcustom org-show-context-detail '((agenda . local)
+ (bookmark-jump . lineage)
+ (isearch . lineage)
+ (default . ancestors))
+ "Alist between context and visibility span when revealing a location.
+
+\\Some actions may move point into invisible
+locations. As a consequence, Org always expose a neighborhood
+around point. How much is shown depends on the initial action,
+or context. Valid contexts are
+
agenda when exposing an entry from the agenda
- org-goto when using the command `org-goto' on key C-c C-j
- occur-tree when using the command `org-occur' on key C-c /
+ org-goto when using the command `org-goto' (`\\[org-goto]')
+ occur-tree when using the command `org-occur' (`\\[org-sparse-tree] /')
tags-tree when constructing a sparse tree based on tags matches
link-search when exposing search matches associated with a link
mark-goto when exposing the jump goal of a mark
bookmark-jump when exposing a bookmark location
isearch when exiting from an incremental search
- default default for all contexts not set explicitly"
- :group 'org-reveal-location
- :type org-context-choice)
-
-(defcustom org-show-following-heading '((default . nil))
- "Non-nil means show following heading when revealing a location.
-Org-mode often shows locations in an org-mode file which might have
-been invisible before. When this is set, the heading following the
-match is shown.
-Turning this off for example for sparse trees makes them very compact,
-but makes it harder to edit the location of the match. In such a case,
-use the command \\[org-reveal] to show more context.
-Instead of t, this can also be an alist specifying this option for different
-contexts. See `org-show-hierarchy-above' for valid contexts."
- :group 'org-reveal-location
- :type org-context-choice)
-
-(defcustom org-show-siblings '((default . nil) (isearch t) (bookmark-jump t))
- "Non-nil means show all sibling heading when revealing a location.
-Org-mode often shows locations in an org-mode file which might have
-been invisible before. When this is set, the sibling of the current entry
-heading are all made visible. If `org-show-hierarchy-above' is t,
-the same happens on each level of the hierarchy above the current entry.
-
-By default this is on for the isearch context, off for all other contexts.
-Turning this off for example for sparse trees makes them very compact,
-but makes it harder to edit the location of the match. In such a case,
-use the command \\[org-reveal] to show more context.
-Instead of t, this can also be an alist specifying this option for different
-contexts. See `org-show-hierarchy-above' for valid contexts."
- :group 'org-reveal-location
- :type org-context-choice
- :version "24.4"
- :package-version '(Org . "8.0"))
+ default default for all contexts not set explicitly
+
+Allowed visibility spans are
+
+ minimal show current headline; if point is not on headline,
+ also show entry
-(defcustom org-show-entry-below '((default . nil))
- "Non-nil means show the entry below a headline when revealing a location.
-Org-mode often shows locations in an org-mode file which might have
-been invisible before. When this is set, the text below the headline that is
-exposed is also shown.
+ local show current headline, entry and next headline
-By default this is off for all contexts.
-Instead of t, this can also be an alist specifying this option for different
-contexts. See `org-show-hierarchy-above' for valid contexts."
+ ancestors show current headline and its direct ancestors; if
+ point is not on headline, also show entry
+
+ lineage show current headline, its direct ancestors and all
+ their children; if point is not on headline, also show
+ entry and first child
+
+ tree show current headline, its direct ancestors and all
+ their children; if point is not on headline, also show
+ entry and all children
+
+ canonical show current headline, its direct ancestors along with
+ their entries and children; if point is not located on
+ the headline, also show current entry and all children
+
+As special cases, a nil or t value means show all contexts in
+`minimal' or `canonical' view, respectively.
+
+Some views can make displayed information very compact, but also
+make it harder to edit the location of the match. In such
+a case, use the command `org-reveal' (`\\[org-reveal]') to show
+more context."
:group 'org-reveal-location
- :type org-context-choice)
+ :version "26.1"
+ :package-version '(Org . "9.0")
+ :type '(choice
+ (const :tag "Canonical" t)
+ (const :tag "Minimal" nil)
+ (repeat :greedy t :tag "Individual contexts"
+ (cons
+ (choice :tag "Context"
+ (const agenda)
+ (const org-goto)
+ (const occur-tree)
+ (const tags-tree)
+ (const link-search)
+ (const mark-goto)
+ (const bookmark-jump)
+ (const isearch)
+ (const default))
+ (choice :tag "Detail level"
+ (const minimal)
+ (const local)
+ (const ancestors)
+ (const lineage)
+ (const tree)
+ (const canonical))))))
(defcustom org-indirect-buffer-display 'other-window
"How should indirect tree buffers be displayed?
+
This applies to indirect buffers created with the commands
-\\[org-tree-to-indirect-buffer] and \\[org-agenda-tree-to-indirect-buffer].
+`org-tree-to-indirect-buffer' and `org-agenda-tree-to-indirect-buffer'.
+
Valid values are:
current-window Display in the current window
other-window Just display in another window.
@@ -1024,7 +1256,13 @@ new-frame Make a new frame each time. Note that in this case
(defcustom org-use-speed-commands nil
"Non-nil means activate single letter commands at beginning of a headline.
This may also be a function to test for appropriate locations where speed
-commands should be active."
+commands should be active.
+
+For example, to activate speed commands when the point is on any
+star at the beginning of the headline, you can do this:
+
+ (setq org-use-speed-commands
+ (lambda () (and (looking-at org-outline-regexp) (looking-back \"^\\**\"))))"
:group 'org-structure
:type '(choice
(const :tag "Never" nil)
@@ -1054,10 +1292,10 @@ commands in the Help buffer using the `?' speed command."
(sexp))))))
(defcustom org-bookmark-names-plist
- '(:last-capture "org-capture-last-stored"
- :last-refile "org-refile-last-stored"
- :last-capture-marker "org-capture-last-stored-marker")
- "Names for bookmarks automatically set by some Org commands.
+ '(:last-capture "org-capture-last-stored"
+ :last-refile "org-refile-last-stored"
+ :last-capture-marker "org-capture-last-stored-marker")
+ "Names for bookmarks automatically set by some Org commands.
This can provide strings as names for a number of bookmarks Org sets
automatically. The following keys are currently implemented:
:last-capture
@@ -1065,11 +1303,11 @@ automatically. The following keys are currently implemented:
:last-refile
When a key does not show up in the property list, the corresponding bookmark
is not set."
- :group 'org-structure
- :type 'plist)
+ :group 'org-structure
+ :type 'plist)
(defgroup org-cycle nil
- "Options concerning visibility cycling in Org-mode."
+ "Options concerning visibility cycling in Org mode."
:tag "Org Cycle"
:group 'org-structure)
@@ -1093,25 +1331,8 @@ than its value."
(const :tag "No limit" nil)
(integer :tag "Maximum level")))
-(defcustom org-drawers '("PROPERTIES" "CLOCK" "LOGBOOK" "RESULTS")
- "Names of drawers. Drawers are not opened by cycling on the headline above.
-Drawers only open with a TAB on the drawer line itself. A drawer looks like
-this:
- :DRAWERNAME:
- .....
- :END:
-The drawer \"PROPERTIES\" is special for capturing properties through
-the property API.
-
-Drawers can be defined on the per-file basis with a line like:
-
-#+DRAWERS: HIDDEN STATE PROPERTIES"
- :group 'org-structure
- :group 'org-cycle
- :type '(repeat (string :tag "Drawer Name")))
-
(defcustom org-hide-block-startup nil
- "Non-nil means entering Org-mode will fold all blocks.
+ "Non-nil means entering Org mode will fold all blocks.
This can also be set in on a per-file basis with
#+STARTUP: hideblocks
@@ -1122,12 +1343,17 @@ This can also be set in on a per-file basis with
(defcustom org-cycle-global-at-bob nil
"Cycle globally if cursor is at beginning of buffer and not at a headline.
-This makes it possible to do global cycling without having to use S-TAB or
-\\[universal-argument] TAB. For this special case to work, the first line
-of the buffer must not be a headline -- it may be empty or some other text.
+
+This makes it possible to do global cycling without having to use `S-TAB'
+or `\\[universal-argument] TAB'. For this special case to work, the first \
+line of the buffer
+must not be a headline -- it may be empty or some other text.
+
When used in this way, `org-cycle-hook' is disabled temporarily to make
-sure the cursor stays at the beginning of the buffer. When this option is
-nil, don't do anything special at the beginning of the buffer."
+sure the cursor stays at the beginning of the buffer.
+
+When this option is nil, don't do anything special at the beginning of
+the buffer."
:group 'org-cycle
:type 'boolean)
@@ -1166,7 +1392,7 @@ visibility is cycled."
"Number of empty lines needed to keep an empty line between collapsed trees.
If you leave an empty line between the end of a subtree and the following
headline, this empty line is hidden when the subtree is folded.
-Org-mode will leave (exactly) one empty line visible if the number of
+Org mode will leave (exactly) one empty line visible if the number of
empty lines is equal or larger to the number given in this variable.
So the default 2 means at least 2 empty lines after the end of a subtree
are needed to produce free space between a collapsed subtree and the
@@ -1192,7 +1418,6 @@ the values `folded', `children', or `subtree'."
(defcustom org-cycle-hook '(org-cycle-hide-archived-subtrees
org-cycle-hide-drawers
- org-cycle-hide-inline-tasks
org-cycle-show-empty-lines
org-optimize-window-after-visibility-change)
"Hook that is run after `org-cycle' has changed the buffer visibility.
@@ -1202,10 +1427,12 @@ argument is a symbol. After a global state change, it can have the values
`overview', `contents', or `all'. After a local state change, it can have
the values `folded', `children', or `subtree'."
:group 'org-cycle
- :type 'hook)
+ :type 'hook
+ :version "26.1"
+ :package-version '(Org . "8.3"))
(defgroup org-edit-structure nil
- "Options concerning structure editing in Org-mode."
+ "Options concerning structure editing in Org mode."
:tag "Org Edit Structure"
:group 'org-structure)
@@ -1229,23 +1456,25 @@ lines to the buffer:
"Non-nil means adapt indentation to outline node level.
When this variable is set, Org assumes that you write outlines by
-indenting text in each node to align with the headline (after the stars).
-The following issues are influenced by this variable:
+indenting text in each node to align with the headline (after the
+stars). The following issues are influenced by this variable:
-- When this is set and the *entire* text in an entry is indented, the
- indentation is increased by one space in a demotion command, and
- decreased by one in a promotion command. If any line in the entry
- body starts with text at column 0, indentation is not changed at all.
+- The indentation is increased by one space in a demotion
+ command, and decreased by one in a promotion command. However,
+ in the latter case, if shifting some line in the entry body
+ would alter document structure (e.g., insert a new headline),
+ indentation is not changed at all.
-- Property drawers and planning information is inserted indented when
- this variable s set. When nil, they will not be indented.
+- Property drawers and planning information is inserted indented
+ when this variable is set. When nil, they will not be indented.
-- TAB indents a line relative to context. The lines below a headline
- will be indented when this variable is set.
+- TAB indents a line relative to current level. The lines below
+ a headline will be indented when this variable is set.
-Note that this is all about true indentation, by adding and removing
-space characters. See also `org-indent.el' which does level-dependent
-indentation in a virtual way, i.e. at display time in Emacs."
+Note that this is all about true indentation, by adding and
+removing space characters. See also `org-indent.el' which does
+level-dependent indentation in a virtual way, i.e. at display
+time in Emacs."
:group 'org-edit-structure
:type 'boolean)
@@ -1286,7 +1515,7 @@ This may also be a cons cell where the behavior for `C-a' and
(const :tag "off" nil)
(const :tag "on: before tags first" t)
(const :tag "reversed: after tags first" reversed)))))
-(org-defvaralias 'org-special-ctrl-a 'org-special-ctrl-a/e)
+(defvaralias 'org-special-ctrl-a 'org-special-ctrl-a/e)
(defcustom org-special-ctrl-k nil
"Non-nil means `C-k' will behave specially in headlines.
@@ -1386,9 +1615,11 @@ default the value to be used for all contexts not explicitly
(defcustom org-insert-heading-respect-content nil
"Non-nil means insert new headings after the current subtree.
+\\
When nil, the new heading is created directly after the current line.
-The commands \\[org-insert-heading-respect-content] and \\[org-insert-todo-heading-respect-content] turn
-this variable on for the duration of the command."
+The commands `\\[org-insert-heading-respect-content]' and \
+`\\[org-insert-todo-heading-respect-content]' turn this variable on
+for the duration of the command."
:group 'org-structure
:type 'boolean)
@@ -1398,11 +1629,7 @@ this variable on for the duration of the command."
The value is an alist, with `heading' and `plain-list-item' as CAR,
and a boolean flag as CDR. The cdr may also be the symbol `auto', in
which case Org will look at the surrounding headings/items and try to
-make an intelligent decision whether to insert a blank line or not.
-
-For plain lists, if `org-list-empty-line-terminates-plain-lists' is set,
-the setting here is ignored and no empty line is inserted to avoid breaking
-the list structure."
+make an intelligent decision whether to insert a blank line or not."
:group 'org-edit-structure
:type '(list
(cons (const heading)
@@ -1422,8 +1649,7 @@ the list structure."
(defcustom org-enable-fixed-width-editor t
"Non-nil means lines starting with \":\" are treated as fixed-width.
This currently only means they are never auto-wrapped.
-When nil, such lines will be treated like ordinary lines.
-See also the QUOTE keyword."
+When nil, such lines will be treated like ordinary lines."
:group 'org-edit-structure
:type 'boolean)
@@ -1441,7 +1667,7 @@ When nil, you can use these keybindings to navigate the buffer:
:type 'boolean)
(defgroup org-sparse-trees nil
- "Options concerning sparse trees in Org-mode."
+ "Options concerning sparse trees in Org mode."
:tag "Org Sparse Trees"
:group 'org-structure)
@@ -1454,14 +1680,26 @@ changed by an edit command."
(defcustom org-remove-highlights-with-change t
"Non-nil means any change to the buffer will remove temporary highlights.
+\\\
Such highlights are created by `org-occur' and `org-clock-display'.
-When nil, `C-c C-c' needs to be used to get rid of the highlights.
-The highlights created by `org-preview-latex-fragment' always need
-`C-c C-c' to be removed."
+When nil, `\\[org-ctrl-c-ctrl-c]' needs to be used \
+to get rid of the highlights.
+The highlights created by `org-toggle-latex-fragment' always need
+`\\[org-toggle-latex-fragment]' to be removed."
:group 'org-sparse-trees
:group 'org-time
:type 'boolean)
+(defcustom org-occur-case-fold-search t
+ "Non-nil means `org-occur' should be case-insensitive.
+If set to `smart' the search will be case-insensitive only if it
+doesn't specify any upper case character."
+ :group 'org-sparse-trees
+ :version "26.1"
+ :type '(choice
+ (const :tag "Case-sensitive" nil)
+ (const :tag "Case-insensitive" t)
+ (const :tag "Case-insensitive for lower case searches only" 'smart)))
(defcustom org-occur-hook '(org-first-headline-recenter)
"Hook that is run after `org-occur' has constructed a sparse tree.
@@ -1471,18 +1709,18 @@ as possible."
:type 'hook)
(defgroup org-imenu-and-speedbar nil
- "Options concerning imenu and speedbar in Org-mode."
+ "Options concerning imenu and speedbar in Org mode."
:tag "Org Imenu and Speedbar"
:group 'org-structure)
(defcustom org-imenu-depth 2
- "The maximum level for Imenu access to Org-mode headlines.
+ "The maximum level for Imenu access to Org headlines.
This also applied for speedbar access."
:group 'org-imenu-and-speedbar
:type 'integer)
(defgroup org-table nil
- "Options concerning tables in Org-mode."
+ "Options concerning tables in Org mode."
:tag "Org Table"
:group 'org)
@@ -1499,12 +1737,12 @@ do the following:
TAB or RET are pressed to move to another field. With optimization this
happens only if changes to a field might have changed the column width.
Optimization requires replacing the functions `self-insert-command',
-`delete-char', and `backward-delete-char' in Org-mode buffers, with a
-slight (in fact: unnoticeable) speed impact for normal typing. Org-mode is
-very good at guessing when a re-align will be necessary, but you can always
-force one with \\[org-ctrl-c-ctrl-c].
+`delete-char', and `backward-delete-char' in Org buffers, with a
+slight (in fact: unnoticeable) speed impact for normal typing. Org is very
+good at guessing when a re-align will be necessary, but you can always
+force one with `\\[org-ctrl-c-ctrl-c]'.
-If you would like to use the optimized version in Org-mode, but the
+If you would like to use the optimized version in Org mode, but the
un-optimized version in OrgTbl-mode, see the variable `orgtbl-optimized'.
This variable can be used to turn on and off the table editor during a session,
@@ -1517,8 +1755,7 @@ See also the variable `org-table-auto-blank-field'."
(const :tag "on" t)
(const :tag "on, optimized" optimized)))
-(defcustom org-self-insert-cluster-for-undo (or (featurep 'xemacs)
- (version<= emacs-version "24.1"))
+(defcustom org-self-insert-cluster-for-undo nil
"Non-nil means cluster self-insert commands for undo when possible.
If this is set, then, like in the Emacs command loop, 20 consecutive
characters will be undone together.
@@ -1534,24 +1771,95 @@ calls `table-recognize-table'."
:type 'boolean)
(defgroup org-link nil
- "Options concerning links in Org-mode."
+ "Options concerning links in Org mode."
:tag "Org Link"
:group 'org)
-(defvar org-link-abbrev-alist-local nil
+(defvar-local org-link-abbrev-alist-local nil
"Buffer-local version of `org-link-abbrev-alist', which see.
The value of this is taken from the #+LINK lines.")
-(make-variable-buffer-local 'org-link-abbrev-alist-local)
+
+(defcustom org-link-parameters
+ '(("doi" :follow org--open-doi-link)
+ ("elisp" :follow org--open-elisp-link)
+ ("file" :complete org-file-complete-link)
+ ("ftp" :follow (lambda (path) (browse-url (concat "ftp:" path))))
+ ("help" :follow org--open-help-link)
+ ("http" :follow (lambda (path) (browse-url (concat "http:" path))))
+ ("https" :follow (lambda (path) (browse-url (concat "https:" path))))
+ ("mailto" :follow (lambda (path) (browse-url (concat "mailto:" path))))
+ ("message" :follow (lambda (path) (browse-url (concat "message:" path))))
+ ("news" :follow (lambda (path) (browse-url (concat "news:" path))))
+ ("shell" :follow org--open-shell-link))
+ "An alist of properties that defines all the links in Org mode.
+The key in each association is a string of the link type.
+Subsequent optional elements make up a p-list of link properties.
+
+:follow - A function that takes the link path as an argument.
+
+:export - A function that takes the link path, description and
+export-backend as arguments.
+
+:store - A function responsible for storing the link. See the
+function `org-store-link-functions'.
+
+:complete - A function that inserts a link with completion. The
+function takes one optional prefix arg.
+
+:face - A face for the link, or a function that returns a face.
+The function takes one argument which is the link path. The
+default face is `org-link'.
+
+:mouse-face - The mouse-face. The default is `highlight'.
+
+:display - `full' will not fold the link in descriptive
+display. Default is `org-link'.
+
+:help-echo - A string or function that takes (window object position)
+as arguments and returns a string.
+
+:keymap - A keymap that is active on the link. The default is
+`org-mouse-map'.
+
+:htmlize-link - A function for the htmlize-link. Defaults
+to (list :uri \"type:path\")
+
+:activate-func - A function to run at the end of font-lock
+activation. The function must accept (link-start link-end path bracketp)
+as arguments."
+ :group 'org-link
+ :type '(alist :tag "Link display parameters"
+ :value-type plist))
+
+(defun org-link-get-parameter (type key)
+ "Get TYPE link property for KEY.
+TYPE is a string and KEY is a plist keyword."
+ (plist-get
+ (cdr (assoc type org-link-parameters))
+ key))
+
+(defun org-link-set-parameters (type &rest parameters)
+ "Set link TYPE properties to PARAMETERS.
+ PARAMETERS should be :key val pairs."
+ (let ((data (assoc type org-link-parameters)))
+ (if data (setcdr data (org-combine-plists (cdr data) parameters))
+ (push (cons type parameters) org-link-parameters)
+ (org-make-link-regexps)
+ (org-element-update-syntax))))
+
+(defun org-link-types ()
+ "Return a list of known link types."
+ (mapcar #'car org-link-parameters))
(defcustom org-link-abbrev-alist nil
"Alist of link abbreviations.
The car of each element is a string, to be replaced at the start of a link.
The cdrs are replacement values, like (\"linkkey\" . REPLACE). Abbreviated
-links in Org-mode buffers can have an optional tag after a double colon, e.g.
+links in Org buffers can have an optional tag after a double colon, e.g.,
[[linkkey:tag][description]]
-The `linkkey' must be a word word, starting with a letter, followed
+The `linkkey' must be a single word, starting with a letter, followed
by letters, numbers, `-' or `_'.
If REPLACE is a string, the tag will simply be appended to create the link.
@@ -1603,11 +1911,18 @@ adaptive Use relative path for files in the current directory and sub-
(const noabbrev)
(const adaptive)))
-(defcustom org-activate-links '(bracket angle plain radio tag date footnote)
- "Types of links that should be activated in Org-mode files.
-This is a list of symbols, each leading to the activation of a certain link
-type. In principle, it does not hurt to turn on most link types - there may
-be a small gain when turning off unused link types. The types are:
+(defvaralias 'org-activate-links 'org-highlight-links)
+(defcustom org-highlight-links '(bracket angle plain radio tag date footnote)
+ "Types of links that should be highlighted in Org files.
+
+This is a list of symbols, each one of them leading to the
+highlighting of a certain link type.
+
+You can still open links that are not highlighted.
+
+In principle, it does not hurt to turn on highlighting for all
+link types. There may be a small gain when turning off unused
+link types. The types are:
bracket The recommended [[link][description]] or [[link]] links with hiding.
angle Links in angular brackets that may contain whitespace like
@@ -1618,8 +1933,10 @@ tag Tag settings in a headline (link to tag search).
date Time stamps (link to calendar).
footnote Footnote labels.
-Changing this variable requires a restart of Emacs to become effective."
+If you set this variable during an Emacs session, use `org-mode-restart'
+in the Org buffer so that the change takes effect."
:group 'org-link
+ :group 'org-appearance
:type '(set :greedy t
(const :tag "Double bracket links" bracket)
(const :tag "Angular bracket links" angle)
@@ -1639,7 +1956,7 @@ return the description to use."
:type '(choice (const nil) (function)))
(defgroup org-link-store nil
- "Options concerning storing links in Org-mode."
+ "Options concerning storing links in Org mode."
:tag "Org Store Link"
:group 'org-link)
@@ -1684,32 +2001,36 @@ It should match if the message is from the user him/herself."
(defcustom org-context-in-file-links t
"Non-nil means file links from `org-store-link' contain context.
-A search string will be added to the file name with :: as separator and
-used to find the context when the link is activated by the command
+\\
+A search string will be added to the file name with :: as separator
+and used to find the context when the link is activated by the command
`org-open-at-point'. When this option is t, the entire active region
will be placed in the search string of the file link. If set to a
positive integer, only the first n lines of context will be stored.
-Using a prefix arg to the command \\[org-store-link] (`org-store-link')
+Using a prefix arg to the command `org-store-link' (`\\[universal-argument] \
+\\[org-store-link]')
negates this setting for the duration of the command."
:group 'org-link-store
:type '(choice boolean integer))
(defcustom org-keep-stored-link-after-insertion nil
"Non-nil means keep link in list for entire session.
-
+\\
The command `org-store-link' adds a link pointing to the current
location to an internal list. These links accumulate during a session.
The command `org-insert-link' can be used to insert links into any
-Org-mode file (offering completion for all stored links). When this
-option is nil, every link which has been inserted once using \\[org-insert-link]
-will be removed from the list, to make completing the unused links
-more efficient."
+Org file (offering completion for all stored links).
+
+When this option is nil, every link which has been inserted once using
+`\\[org-insert-link]' will be removed from the list, to make completing the \
+unused
+links more efficient."
:group 'org-link-store
:type 'boolean)
(defgroup org-link-follow nil
- "Options concerning following links in Org-mode."
+ "Options concerning following links in Org mode."
:tag "Org Follow Link"
:group 'org-link)
@@ -1749,8 +2070,8 @@ In tables, the special behavior of RET has precedence."
(defcustom org-mouse-1-follows-link
(if (boundp 'mouse-1-click-follows-link) mouse-1-click-follows-link t)
"Non-nil means mouse-1 on a link will follow the link.
-A longer mouse click will still set point. Does not work on XEmacs.
-Needs to be set before org.el is loaded."
+A longer mouse click will still set point. Needs to be set
+before org.el is loaded."
:group 'org-link-follow
:version "24.4"
:package-version '(Org . "8.3")
@@ -1766,16 +2087,22 @@ Changing this requires a restart of Emacs to work correctly."
:type 'integer)
(defcustom org-link-search-must-match-exact-headline 'query-to-create
- "Non-nil means internal links in Org files must exactly match a headline.
-When nil, the link search tries to match a phrase with all words
-in the search text."
+ "Non-nil means internal fuzzy links can only match headlines.
+
+When nil, the a fuzzy link may point to a target or a named
+construct in the document. When set to the special value
+`query-to-create', offer to create a new headline when none
+matched.
+
+Spaces and statistics cookies are ignored during heading searches."
:group 'org-link-follow
:version "24.1"
:type '(choice
(const :tag "Use fuzzy text search" nil)
(const :tag "Match only exact headline" t)
(const :tag "Match exact headline or query to create it"
- query-to-create)))
+ query-to-create))
+ :safe #'symbolp)
(defcustom org-link-frame-setup
'((vm . vm-visit-folder-other-frame)
@@ -1836,7 +2163,7 @@ another window."
"Non-nil means use indirect buffer to display infile links.
Activating internal links (from one location in a file to another location
in the same file) normally just jumps to the location. When the link is
-activated with a \\[universal-argument] prefix (or with mouse-3), the link \
+activated with a `\\[universal-argument]' prefix (or with mouse-3), the link \
is displayed in
another window. When this option is set, the other window actually displays
an indirect buffer clone of the current buffer, to avoid any visibility
@@ -1860,26 +2187,13 @@ window on that directory."
:group 'org-link-follow
:type 'boolean)
-(defcustom org-link-mailto-program '(browse-url "mailto:%a?subject=%s")
- "Function and arguments to call for following mailto links.
-This is a list with the first element being a Lisp function, and the
-remaining elements being arguments to the function. In string arguments,
-%a will be replaced by the address, and %s will be replaced by the subject
-if one was given like in ."
- :group 'org-link-follow
- :type '(choice
- (const :tag "browse-url" (browse-url-mail "mailto:%a?subject=%s"))
- (const :tag "compose-mail" (compose-mail "%a" "%s"))
- (const :tag "message-mail" (message-mail "%a" "%s"))
- (cons :tag "other" (function) (repeat :tag "argument" sexp))))
-
(defcustom org-confirm-shell-link-function 'yes-or-no-p
"Non-nil means ask for confirmation before executing shell links.
Shell links can be dangerous: just think about a link
[[shell:rm -rf ~/*][Google Search]]
-This link would show up in your Org-mode document as \"Google Search\",
+This link would show up in your Org document as \"Google Search\",
but really it would remove your entire home directory.
Therefore we advise against setting this variable to nil.
Just change it to `y-or-n-p' if you want to confirm with a
@@ -1891,7 +2205,7 @@ single keystroke rather than having to type \"yes\"."
(const :tag "no confirmation (dangerous)" nil)))
(put 'org-confirm-shell-link-function
'safe-local-variable
- #'(lambda (x) (member x '(yes-or-no-p y-or-n-p))))
+ (lambda (x) (member x '(yes-or-no-p y-or-n-p))))
(defcustom org-confirm-shell-link-not-regexp ""
"A regexp to skip confirmation for shell links."
@@ -1905,7 +2219,7 @@ Elisp links can be dangerous: just think about a link
[[elisp:(shell-command \"rm -rf ~/*\")][Google Search]]
-This link would show up in your Org-mode document as \"Google Search\",
+This link would show up in your Org document as \"Google Search\",
but really it would remove your entire home directory.
Therefore we advise against setting this variable to nil.
Just change it to `y-or-n-p' if you want to confirm with a
@@ -1917,7 +2231,7 @@ single keystroke rather than having to type \"yes\"."
(const :tag "no confirmation (dangerous)" nil)))
(put 'org-confirm-shell-link-function
'safe-local-variable
- #'(lambda (x) (member x '(yes-or-no-p y-or-n-p))))
+ (lambda (x) (member x '(yes-or-no-p y-or-n-p))))
(defcustom org-confirm-elisp-link-not-regexp ""
"A regexp to skip confirmation for Elisp links."
@@ -1934,30 +2248,23 @@ See `org-file-apps'.")
(defconst org-file-apps-defaults-macosx
'((remote . emacs)
- (t . "open %s")
(system . "open %s")
("ps.gz" . "gv %s")
("eps.gz" . "gv %s")
("dvi" . "xdvi %s")
- ("fig" . "xfig %s"))
+ ("fig" . "xfig %s")
+ (t . "open %s"))
"Default file applications on a macOS system.
The system \"open\" is known as a default, but we use X11 applications
for some files for which the OS does not have a good default.
See `org-file-apps'.")
(defconst org-file-apps-defaults-windowsnt
- (list
- '(remote . emacs)
- (cons t
- (list (if (featurep 'xemacs)
- 'mswindows-shell-execute
- 'w32-shell-execute)
- "open" 'file))
- (cons 'system
- (list (if (featurep 'xemacs)
- 'mswindows-shell-execute
- 'w32-shell-execute)
- "open" 'file)))
+ (list '(remote . emacs)
+ (cons 'system (lambda (file _path)
+ (with-no-warnings (w32-shell-execute "open" file))))
+ (cons t (lambda (file _path)
+ (with-no-warnings (w32-shell-execute "open" file)))))
"Default file applications on a Windows NT system.
The system \"open\" is used for most files.
See `org-file-apps'.")
@@ -1968,11 +2275,15 @@ See `org-file-apps'.")
("\\.x?html?\\'" . default)
("\\.pdf\\'" . default))
"External applications for opening `file:path' items in a document.
-Org-mode uses system defaults for different file types, but
+\\\
+
+Org mode uses system defaults for different file types, but
you can use this variable to set the application for a given file
extension. The entries in this list are cons cells where the car identifies
-files and the cdr the corresponding command. Possible values for the
-file identifier are
+files and the cdr the corresponding command.
+
+Possible values for the file identifier are:
+
\"string\" A string as a file identifier can be interpreted in different
ways, depending on its contents:
@@ -1985,8 +2296,8 @@ file identifier are
filename matches the regexp. If you want to
use groups here, use shy groups.
- Example: (\"\\.x?html\\\\='\" . \"firefox %s\")
- (\"\\(?:xhtml\\|html\\)\" . \"firefox %s\")
+ Example: (\"\\\\.x?html\\\\\\='\" . \"firefox %s\")
+ (\"\\\\(?:xhtml\\\\|html\\\\)\\\\\\='\" . \"firefox %s\")
to open *.html and *.xhtml with firefox.
- Regular expression which contains (non-shy) groups:
@@ -1998,10 +2309,11 @@ file identifier are
that does not use any of the group matches, this case is
handled identically to the second one (i.e. match against
file name only).
- In a custom lisp form, you can access the group matches with
+ In a custom function, you can access the group matches with
(match-string n link).
- Example: (\"\\.pdf::\\(\\d+\\)\\\\='\" . \"evince -p %1 %s\")
+ Example: (\"\\\\.pdf::\\\\(\\\\d+\\\\)\\\\\\='\" . \
+\"evince -p %1 %s\")
to open [[file:document.pdf::5]] with evince at page 5.
`directory' Matches a directory
@@ -2013,28 +2325,32 @@ file identifier are
command `emacs' will open most files in Emacs. Beware that this
will also open html files inside Emacs, unless you add
(\"html\" . default) to the list as well.
- t Default for files not matched by any of the other options.
`system' The system command to open files, like `open' on Windows
and macOS, and mailcap under GNU/Linux. This is the command
- that will be selected if you call `C-c C-o' with a double
- \\[universal-argument] \\[universal-argument] prefix.
+ that will be selected if you call `org-open-at-point' with a
+ double prefix argument (`\\[universal-argument] \
+\\[universal-argument] \\[org-open-at-point]').
+ t Default for files not matched by any of the other options.
Possible values for the command are:
+
`emacs' The file will be visited by the current Emacs process.
`default' Use the default application for this file type, which is the
association for t in the list, most likely in the system-specific
- part.
- This can be used to overrule an unwanted setting in the
+ part. This can be used to overrule an unwanted setting in the
system-specific variable.
`system' Use the system command for opening files, like \"open\".
This command is specified by the entry whose car is `system'.
Most likely, the system-specific version of this variable
does define this command, but you can overrule/replace it
here.
+`mailcap' Use command specified in the mailcaps.
string A command to be executed by a shell; %s will be replaced
by the path to the file.
- sexp A Lisp form which will be evaluated. The file path will
- be available in the Lisp variable `file'.
+ function A Lisp function, which will be called with two arguments:
+ the file path and the original link string, without the
+ \"file:\" prefix.
+
For more examples, see the system specific constants
`org-file-apps-defaults-macosx'
`org-file-apps-defaults-windowsnt'
@@ -2054,7 +2370,7 @@ For more examples, see the system specific constants
(const :tag "Use default" default)
(const :tag "Use the system command" system)
(string :tag "Command")
- (sexp :tag "Lisp form")))))
+ (function :tag "Function")))))
(defcustom org-doi-server-url "http://dx.doi.org/"
"The URL of the DOI server."
@@ -2063,22 +2379,22 @@ For more examples, see the system specific constants
:group 'org-link-follow)
(defgroup org-refile nil
- "Options concerning refiling entries in Org-mode."
+ "Options concerning refiling entries in Org mode."
:tag "Org Refile"
:group 'org)
(defcustom org-directory "~/org"
- "Directory with org files.
+ "Directory with Org files.
This is just a default location to look for Org files. There is no need
-at all to put your files into this directory. It is only used in the
+at all to put your files into this directory. It is used in the
following situations:
1. When a capture template specifies a target file that is not an
absolute path. The path will then be interpreted relative to
`org-directory'
-2. When a capture note is filed away in an interactive way (when exiting the
- note buffer with `C-1 C-c C-c'. The user is prompted for an org file,
- with `org-directory' as the default path."
+2. When the value of variable `org-agenda-files' is a single file, any
+ relative paths in this file will be taken as relative to
+ `org-directory'."
:group 'org-refile
:group 'org-capture
:type 'directory)
@@ -2089,9 +2405,7 @@ Used as a fall back file for org-capture.el, for templates that
do not specify a target file."
:group 'org-refile
:group 'org-capture
- :type '(choice
- (const :tag "Default from remember-data-file" nil)
- file))
+ :type 'file)
(defcustom org-goto-interface 'outline
"The default interface to be used for `org-goto'.
@@ -2154,7 +2468,7 @@ will temporarily be changed to `time'."
(const :tag "Record timestamp with note." note)))
(defcustom org-refile-targets nil
- "Targets for refiling entries with \\[org-refile].
+ "Targets for refiling entries with `\\[org-refile]'.
This is a list of cons cells. Each cell contains:
- a specification of the files to be considered, either a list of files,
or a symbol whose function or variable value will be used to retrieve
@@ -2218,12 +2532,15 @@ of the subtree."
(defcustom org-refile-use-cache nil
"Non-nil means cache refile targets to speed up the process.
+\\\
The cache for a particular file will be updated automatically when
the buffer has been killed, or when any of the marker used for flagging
refile targets no longer points at a live buffer.
If you have added new entries to a buffer that might themselves be targets,
-you need to clear the cache manually by pressing `C-0 C-c C-w' or, if you
-find that easier, `C-u C-u C-u C-c C-w'."
+you need to clear the cache manually by pressing `C-0 \\[org-refile]' or,
+if you find that easier, \
+`\\[universal-argument] \\[universal-argument] \\[universal-argument] \
+\\[org-refile]'."
:group 'org-refile
:version "24.1"
:type 'boolean)
@@ -2246,13 +2563,13 @@ When `full-file-path', include the full file path."
(defcustom org-outline-path-complete-in-steps t
"Non-nil means complete the outline path in hierarchical steps.
-When Org-mode uses the refile interface to select an outline path
-\(see variable `org-refile-use-outline-path'), the completion of
-the path can be done is a single go, or if can be done in steps down
-the headline hierarchy. Going in steps is probably the best if you
-do not use a special completion package like `ido' or `icicles'.
-However, when using these packages, going in one step can be very
-fast, while still showing the whole path to the entry."
+When Org uses the refile interface to select an outline path (see
+`org-refile-use-outline-path'), the completion of the path can be
+done in a single go, or it can be done in steps down the headline
+hierarchy. Going in steps is probably the best if you do not use
+a special completion package like `ido' or `icicles'. However,
+when using these packages, going in one step can be very fast,
+while still showing the whole path to the entry."
:group 'org-refile
:type 'boolean)
@@ -2285,12 +2602,12 @@ converted to a headline before refiling."
:type 'boolean)
(defgroup org-todo nil
- "Options concerning TODO items in Org-mode."
+ "Options concerning TODO items in Org mode."
:tag "Org TODO"
:group 'org)
(defgroup org-progress nil
- "Options concerning Progress logging in Org-mode."
+ "Options concerning Progress logging in Org mode."
:tag "Org Progress"
:group 'org-time)
@@ -2308,12 +2625,12 @@ Each sequence starts with a symbol, either `sequence' or `type',
indicating if the keywords should be interpreted as a sequence of
action steps, or as different types of TODO items. The first
keywords are states requiring action - these states will select a headline
-for inclusion into the global TODO list Org-mode produces. If one of
-the \"keywords\" is the vertical bar, \"|\", the remaining keywords
+for inclusion into the global TODO list Org produces. If one of the
+\"keywords\" is the vertical bar, \"|\", the remaining keywords
signify that no further action is necessary. If \"|\" is not found,
the last keyword is treated as the only DONE state of the sequence.
-The command \\[org-todo] cycles an entry through these states, and one
+The command `\\[org-todo]' cycles an entry through these states, and one
additional state where no keyword is present. For details about this
cycling, see the manual.
@@ -2356,44 +2673,37 @@ taken from the (otherwise obsolete) variable `org-todo-interpretation'."
(lambda (widget)
(widget-put widget
:args (mapcar
- #'(lambda (x)
- (widget-convert
- (cons 'const x)))
+ (lambda (x)
+ (widget-convert
+ (cons 'const x)))
org-todo-interpretation-widgets))
widget))
(repeat
(string :tag "Keyword"))))))
-(defvar org-todo-keywords-1 nil
+(defvar-local org-todo-keywords-1 nil
"All TODO and DONE keywords active in a buffer.")
-(make-variable-buffer-local 'org-todo-keywords-1)
(defvar org-todo-keywords-for-agenda nil)
(defvar org-done-keywords-for-agenda nil)
-(defvar org-drawers-for-agenda nil)
(defvar org-todo-keyword-alist-for-agenda nil)
(defvar org-tag-alist-for-agenda nil
"Alist of all tags from all agenda files.")
(defvar org-tag-groups-alist-for-agenda nil
"Alist of all groups tags from all current agenda files.")
-(defvar org-tag-groups-alist nil)
-(make-variable-buffer-local 'org-tag-groups-alist)
+(defvar-local org-tag-groups-alist nil)
(defvar org-agenda-contributing-files nil)
-(defvar org-not-done-keywords nil)
-(make-variable-buffer-local 'org-not-done-keywords)
-(defvar org-done-keywords nil)
-(make-variable-buffer-local 'org-done-keywords)
-(defvar org-todo-heads nil)
-(make-variable-buffer-local 'org-todo-heads)
-(defvar org-todo-sets nil)
-(make-variable-buffer-local 'org-todo-sets)
-(defvar org-todo-log-states nil)
-(make-variable-buffer-local 'org-todo-log-states)
-(defvar org-todo-kwd-alist nil)
-(make-variable-buffer-local 'org-todo-kwd-alist)
-(defvar org-todo-key-alist nil)
-(make-variable-buffer-local 'org-todo-key-alist)
-(defvar org-todo-key-trigger nil)
-(make-variable-buffer-local 'org-todo-key-trigger)
+(defvar-local org-current-tag-alist nil
+ "Alist of all tag groups in current buffer.
+This variable takes into consideration `org-tag-alist',
+`org-tag-persistent-alist' and TAGS keywords in the buffer.")
+(defvar-local org-not-done-keywords nil)
+(defvar-local org-done-keywords nil)
+(defvar-local org-todo-heads nil)
+(defvar-local org-todo-sets nil)
+(defvar-local org-todo-log-states nil)
+(defvar-local org-todo-kwd-alist nil)
+(defvar-local org-todo-key-alist nil)
+(defvar-local org-todo-key-trigger nil)
(defcustom org-todo-interpretation 'sequence
"Controls how TODO keywords are interpreted.
@@ -2407,7 +2717,8 @@ more information."
(const type)))
(defcustom org-use-fast-todo-selection t
- "Non-nil means use the fast todo selection scheme with C-c C-t.
+ "\\\
+Non-nil means use the fast todo selection scheme with `\\[org-todo]'.
This variable describes if and under what circumstances the cycling
mechanism for TODO keywords will be replaced by a single-key, direct
selection scheme.
@@ -2415,8 +2726,9 @@ selection scheme.
When nil, fast selection is never used.
When the symbol `prefix', it will be used when `org-todo' is called
-with a prefix argument, i.e. `C-u C-c C-t' in an Org-mode buffer, and
-`C-u t' in an agenda buffer.
+with a prefix argument, i.e. `\\[universal-argument] \\[org-todo]' \
+in an Org buffer, and
+`\\[universal-argument] t' in an agenda buffer.
When t, fast selection is used by default. In this case, the prefix
argument forces cycling instead.
@@ -2436,6 +2748,9 @@ ALL-HEADLINES means update todo statistics by including headlines
with no TODO keyword as well, counting them as not done.
A list of TODO keywords means the same, but skip keywords that are
not in this list.
+When set to a list of two lists, the first list contains keywords
+to consider as TODO keywords, the second list contains keywords
+to consider as DONE keywords.
When this is set, todo statistics is updated in the parent of the
current entry each time a todo state is changed."
@@ -2445,6 +2760,9 @@ current entry each time a todo state is changed."
(const :tag "Yes, including all entries" all-headlines)
(repeat :tag "Yes, for TODOs in this list"
(string :tag "TODO keyword"))
+ (list :tag "Yes, for TODOs and DONEs in these lists"
+ (repeat (string :tag "TODO keyword"))
+ (repeat (string :tag "DONE keyword")))
(other :tag "No TODO statistics" nil)))
(defcustom org-hierarchical-todo-statistics t
@@ -2529,7 +2847,7 @@ to change is while Emacs is running is through the customize interface."
(defcustom org-treat-insert-todo-heading-as-state-change nil
"Non-nil means inserting a TODO heading is treated as state change.
-So when the command \\[org-insert-todo-heading] is used, state change
+So when the command `\\[org-insert-todo-heading]' is used, state change
logging will apply if appropriate. When nil, the new TODO item will
be inserted directly, and no logging will take place."
:group 'org-todo
@@ -2667,20 +2985,23 @@ When nil, only the date will be recorded."
(refile . "Refiled on %t")
(clock-out . ""))
"Headings for notes added to entries.
-The value is an alist, with the car being a symbol indicating the note
-context, and the cdr is the heading to be used. The heading may also be the
-empty string.
-%t in the heading will be replaced by a time stamp.
-%T will be an active time stamp instead the default inactive one
-%d will be replaced by a short-format time stamp.
-%D will be replaced by an active short-format time stamp.
-%s will be replaced by the new TODO state, in double quotes.
-%S will be replaced by the old TODO state, in double quotes.
-%u will be replaced by the user name.
-%U will be replaced by the full user name.
-
-In fact, it is not a good idea to change the `state' entry, because
-agenda log mode depends on the format of these entries."
+
+The value is an alist, with the car being a symbol indicating the
+note context, and the cdr is the heading to be used. The heading
+may also be the empty string. The following placeholders can be
+used:
+
+ %t a time stamp.
+ %T an active time stamp instead the default inactive one
+ %d a short-format time stamp.
+ %D an active short-format time stamp.
+ %s the new TODO state or time stamp (inactive), in double quotes.
+ %S the old TODO state or time stamp (inactive), in double quotes.
+ %u the user name.
+ %U full user name.
+
+In fact, it is not a good idea to change the `state' entry,
+because Agenda Log mode depends on the format of these entries."
:group 'org-todo
:group 'org-progress
:type '(list :greedy t
@@ -2719,7 +3040,10 @@ If this variable is set, `org-log-state-notes-insert-after-drawers'
will be ignored.
You can set the property LOG_INTO_DRAWER to overrule this setting for
-a subtree."
+a subtree.
+
+Do not check directly this variable in a Lisp program. Call
+function `org-log-into-drawer' instead."
:group 'org-todo
:group 'org-progress
:type '(choice
@@ -2727,18 +3051,20 @@ a subtree."
(const :tag "LOGBOOK" t)
(string :tag "Other")))
-(org-defvaralias 'org-log-state-notes-into-drawer 'org-log-into-drawer)
+(defvaralias 'org-log-state-notes-into-drawer 'org-log-into-drawer)
(defun org-log-into-drawer ()
- "Return the value of `org-log-into-drawer', but let properties overrule.
-If the current entry has or inherits a LOG_INTO_DRAWER property, it will be
-used instead of the default value."
+ "Name of the log drawer, as a string, or nil.
+This is the value of `org-log-into-drawer'. However, if the
+current entry has or inherits a LOG_INTO_DRAWER property, it will
+be used instead of the default value."
(let ((p (org-entry-get nil "LOG_INTO_DRAWER" 'inherit t)))
- (cond
- ((not p) org-log-into-drawer)
- ((equal p "nil") nil)
- ((equal p "t") "LOGBOOK")
- (t p))))
+ (cond ((equal p "nil") nil)
+ ((equal p "t") "LOGBOOK")
+ ((stringp p) p)
+ (p "LOGBOOK")
+ ((stringp org-log-into-drawer) org-log-into-drawer)
+ (org-log-into-drawer "LOGBOOK"))))
(defcustom org-log-state-notes-insert-after-drawers nil
"Non-nil means insert state change notes after any drawers in entry.
@@ -2804,7 +3130,7 @@ property to one or more of these keywords."
(defgroup org-priorities nil
- "Priorities in Org-mode."
+ "Priorities in Org mode."
:tag "Org Priorities"
:group 'org-todo)
@@ -2862,24 +3188,13 @@ as an argument and return the numeric priority."
(function)))
(defgroup org-time nil
- "Options concerning time stamps and deadlines in Org-mode."
+ "Options concerning time stamps and deadlines in Org mode."
:tag "Org Time"
:group 'org)
-(defcustom org-insert-labeled-timestamps-at-point nil
- "Non-nil means SCHEDULED and DEADLINE timestamps are inserted at point.
-When nil, these labeled time stamps are forces into the second line of an
-entry, just after the headline. When scheduling from the global TODO list,
-the time stamp will always be forced into the second line."
- :group 'org-time
- :type 'boolean)
-
-(defconst org-time-stamp-formats '("<%Y-%m-%d %a>" . "<%Y-%m-%d %a %H:%M>")
- "Formats for `format-time-string' which are used for time stamps.
-It is not recommended to change this constant.")
-
(defcustom org-time-stamp-rounding-minutes '(0 5)
"Number of minutes to round time stamps to.
+\\\
These are two values, the first applies when first creating a time stamp.
The second applies when changing it with the commands `S-up' and `S-down'.
When changing the time stamp, this means that it will change in steps
@@ -2889,14 +3204,15 @@ When a setting is 0 or 1, insert the time unmodified. Useful rounding
numbers should be factors of 60, so for example 5, 10, 15.
When this is larger than 1, you can still force an exact time stamp by using
-a double prefix argument to a time stamp command like `C-c .' or `C-c !',
+a double prefix argument to a time stamp command like \
+`\\[org-time-stamp]' or `\\[org-time-stamp-inactive],
and by using a prefix arg to `S-up/down' to specify the exact number
of minutes to shift."
:group 'org-time
- :get #'(lambda (var) ; Make sure both elements are there
- (if (integerp (default-value var))
- (list (default-value var) 5)
- (default-value var)))
+ :get (lambda (var) ; Make sure both elements are there
+ (if (integerp (default-value var))
+ (list (default-value var) 5)
+ (default-value var)))
:type '(list
(integer :tag "when inserting times")
(integer :tag "when modifying times")))
@@ -3013,7 +3329,7 @@ in minutes (even for durations longer than an hour)."
(const t)))))
(defcustom org-time-clocksum-use-fractional nil
- "When non-nil, \\[org-clock-display] uses fractional times.
+ "When non-nil, `\\[org-clock-display]' uses fractional times.
See `org-time-clocksum-format' for more on time clock formats."
:group 'org-time
:group 'org-clock
@@ -3021,7 +3337,7 @@ See `org-time-clocksum-format' for more on time clock formats."
:type 'boolean)
(defcustom org-time-clocksum-use-effort-durations nil
- "When non-nil, \\[org-clock-display] uses effort durations.
+ "When non-nil, `\\[org-clock-display]' uses effort durations.
E.g. by default, one day is considered to be a 8 hours effort,
so a task that has been clocked for 16 hours will be displayed
as during 2 days in the clock display or in the clocktable.
@@ -3052,9 +3368,9 @@ is used."
:group 'org-time
:type '(choice (string :tag "Format string")
(set (group :inline t (const :tag "Years" :years)
- (string :tag "Format string"))
+ (string :tag "Format string"))
(group :inline t (const :tag "Months" :months)
- (string :tag "Format string"))
+ (string :tag "Format string"))
(group :inline t (const :tag "Weeks" :weeks)
(string :tag "Format string"))
(group :inline t (const :tag "Days" :days)
@@ -3097,8 +3413,8 @@ This affects the following situations:
For example, if it is April and you enter \"feb 2\", this will be read
as Feb 2, *next* year. \"May 5\", however, will be this year.
2. The user gives a day, but no month.
- For example, if today is the 15th, and you enter \"3\", Org-mode will
- read this as the third of *next* month. However, if you enter \"17\",
+ For example, if today is the 15th, and you enter \"3\", Org will read
+ this as the third of *next* month. However, if you enter \"17\",
it will be considered as *this* month.
If you set this variable to the symbol `time', then also the following
@@ -3176,22 +3492,9 @@ In the calendar, the date can be selected with mouse-1. However, the
minibuffer will also be active, and you can simply enter the date as well.
When nil, only the minibuffer will be available."
:group 'org-time
- :type 'boolean)
-(org-defvaralias 'org-popup-calendar-for-date-prompt
- 'org-read-date-popup-calendar)
-
-(make-obsolete-variable
- 'org-read-date-minibuffer-setup-hook
- "Set `org-read-date-minibuffer-local-map' instead." "24.4")
-(defcustom org-read-date-minibuffer-setup-hook nil
- "Hook to be used to set up keys for the date/time interface.
-Add key definitions to `minibuffer-local-map', which will be a
-temporary copy.
-
-WARNING: This option is obsolete, you should use
-`org-read-date-minibuffer-local-map' to set up keys."
- :group 'org-time
- :type 'hook)
+ :type 'boolean)
+(defvaralias 'org-popup-calendar-for-date-prompt
+ 'org-read-date-popup-calendar)
(defcustom org-extend-today-until 0
"The hour when your day really ends. Must be an integer.
@@ -3240,52 +3543,76 @@ moved to the new date."
:type 'boolean)
(defgroup org-tags nil
- "Options concerning tags in Org-mode."
+ "Options concerning tags in Org mode."
:tag "Org Tags"
:group 'org)
(defcustom org-tag-alist nil
- "List of tags allowed in Org-mode files.
-When this list is nil, Org-mode will base TAG input on what is already in the
-buffer.
-The value of this variable is an alist, the car of each entry must be a
-keyword as a string, the cdr may be a character that is used to select
-that tag through the fast-tag-selection interface.
-See the manual for details."
+ "Default tags available in Org files.
+
+The value of this variable is an alist. Associations either:
+
+ (TAG)
+ (TAG . SELECT)
+ (SPECIAL)
+
+where TAG is a tag as a string, SELECT is character, used to
+select that tag through the fast tag selection interface, and
+SPECIAL is one of the following keywords: `:startgroup',
+`:startgrouptag', `:grouptags', `:engroup', `:endgrouptag' or
+`:newline'. These keywords are used to define a hierarchy of
+tags. See manual for details.
+
+When this variable is nil, Org mode bases tag input on what is
+already in the buffer. The value can be overridden locally by
+using a TAGS keyword, e.g.,
+
+ #+TAGS: tag1 tag2
+
+See also `org-tag-persistent-alist' to sidestep this behavior."
:group 'org-tags
:type '(repeat
(choice
(cons (string :tag "Tag name")
(character :tag "Access char"))
- (list :tag "Start radio group"
- (const :startgroup)
- (option (string :tag "Group description")))
- (list :tag "Group tags delimiter"
- (const :grouptags))
- (list :tag "End radio group"
- (const :endgroup)
- (option (string :tag "Group description")))
+ (const :tag "Start radio group" (:startgroup))
+ (const :tag "Start tag group, non distinct" (:startgrouptag))
+ (const :tag "Group tags delimiter" (:grouptags))
+ (const :tag "End radio group" (:endgroup))
+ (const :tag "End tag group, non distinct" (:endgrouptag))
(const :tag "New line" (:newline)))))
(defcustom org-tag-persistent-alist nil
- "List of tags that will always appear in all Org-mode files.
-This is in addition to any in buffer settings or customizations
-of `org-tag-alist'.
-When this list is nil, Org-mode will base TAG input on `org-tag-alist'.
-The value of this variable is an alist, the car of each entry must be a
-keyword as a string, the cdr may be a character that is used to select
-that tag through the fast-tag-selection interface.
-See the manual for details.
-To disable these tags on a per-file basis, insert anywhere in the file:
- #+STARTUP: noptag"
+ "Tags always available in Org files.
+
+The value of this variable is an alist. Associations either:
+
+ (TAG)
+ (TAG . SELECT)
+ (SPECIAL)
+
+where TAG is a tag as a string, SELECT is a character, used to
+select that tag through the fast tag selection interface, and
+SPECIAL is one of the following keywords: `:startgroup',
+`:startgrouptag', `:grouptags', `:engroup', `:endgrouptag' or
+`:newline'. These keywords are used to define a hierarchy of
+tags. See manual for details.
+
+Unlike to `org-tag-alist', tags defined in this variable do not
+depend on a local TAGS keyword. Instead, to disable these tags
+on a per-file basis, insert anywhere in the file:
+
+ #+STARTUP: noptag"
:group 'org-tags
:type '(repeat
(choice
- (cons (string :tag "Tag name")
- (character :tag "Access char"))
+ (cons (string :tag "Tag name")
+ (character :tag "Access char"))
(const :tag "Start radio group" (:startgroup))
+ (const :tag "Start tag group, non distinct" (:startgrouptag))
(const :tag "Group tags delimiter" (:grouptags))
(const :tag "End radio group" (:endgroup))
+ (const :tag "End tag group, non distinct" (:endgrouptag))
(const :tag "New line" (:newline)))))
(defcustom org-complete-tags-always-offer-all-agenda-tags nil
@@ -3296,9 +3623,7 @@ tags in that file can be created dynamically (there are none).
(add-hook \\='org-capture-mode-hook
(lambda ()
- (set (make-local-variable
- \\='org-complete-tags-always-offer-all-agenda-tags)
- t)))"
+ (setq-local org-complete-tags-always-offer-all-agenda-tags t)))"
:group 'org-tags
:version "24.1"
:type 'boolean)
@@ -3340,7 +3665,7 @@ displaying the tags menu is not even shown, until you press C-c again."
"Non-nil means fast tags selection interface will also offer TODO states.
This is an undocumented feature, you should not rely on it.")
-(defcustom org-tags-column (if (featurep 'xemacs) -76 -77)
+(defcustom org-tags-column -77
"The column to which tags should be indented in a headline.
If this number is positive, it specifies the column. If it is negative,
it means that the tags should be flushright to that column. For example,
@@ -3437,7 +3762,7 @@ is better to limit inheritance to certain tags using the variables
"Hook that is run after the tags in a line have changed.")
(defgroup org-properties nil
- "Options concerning properties in Org-mode."
+ "Options concerning properties in Org mode."
:tag "Org Properties"
:group 'org)
@@ -3504,14 +3829,14 @@ in this variable)."
(regexp :tag "Properties matched by regexp")))
(defun org-property-inherit-p (property)
- "Check if PROPERTY is one that should be inherited."
+ "Return a non-nil value if PROPERTY should be inherited."
(cond
((eq org-use-property-inheritance t) t)
((not org-use-property-inheritance) nil)
((stringp org-use-property-inheritance)
(string-match org-use-property-inheritance property))
((listp org-use-property-inheritance)
- (member property org-use-property-inheritance))
+ (member-ignore-case property org-use-property-inheritance))
(t (error "Invalid setting of `org-use-property-inheritance'"))))
(defcustom org-columns-default-format "%25ITEM %TODO %3PRIORITY %TAGS"
@@ -3532,26 +3857,6 @@ ellipses string, only part of the ellipses string will be shown."
:group 'org-properties
:type 'string)
-(defcustom org-columns-modify-value-for-display-function nil
- "Function that modifies values for display in column view.
-For example, it can be used to cut out a certain part from a time stamp.
-The function must take 2 arguments:
-
-column-title The title of the column (*not* the property name)
-value The value that should be modified.
-
-The function should return the value that should be displayed,
-or nil if the normal value should be used."
- :group 'org-properties
- :type '(choice (const nil) (function)))
-
-(defcustom org-effort-property "Effort"
- "The property that is being used to keep track of effort estimates.
-Effort estimates given in this property need to have the format H:MM."
- :group 'org-properties
- :group 'org-progress
- :type '(string :tag "Property"))
-
(defconst org-global-properties-fixed
'(("VISIBILITY_ALL" . "folded children content all")
("CLOCK_MODELINE_TOTAL_ALL" . "current today repeat all auto"))
@@ -3582,18 +3887,17 @@ You can set buffer-local values for the same purpose in the variable
(cons (string :tag "Property")
(string :tag "Value"))))
-(defvar org-file-properties nil
+(defvar-local org-file-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.")
-(make-variable-buffer-local 'org-file-properties)
(defgroup org-agenda nil
- "Options concerning agenda views in Org-mode."
+ "Options concerning agenda views in Org mode."
:tag "Org Agenda"
:group 'org)
-(defvar org-category nil
+(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
@@ -3605,22 +3909,22 @@ or contain a special line
If the file does not specify a category, then file's base name
is used instead.")
-(make-variable-buffer-local 'org-category)
-(put 'org-category 'safe-local-variable #'(lambda (x) (or (symbolp x) (stringp x))))
+(put 'org-category 'safe-local-variable (lambda (x) (or (symbolp x) (stringp x))))
(defcustom org-agenda-files nil
"The files to be used for agenda display.
-Entries may be added to this list with \\[org-agenda-file-to-front] and removed with
-\\[org-remove-file]. You can also use customize to edit the list.
-If an entry is a directory, all files in that directory that are matched by
-`org-agenda-file-regexp' will be part of the file list.
+If an entry is a directory, all files in that directory that are matched
+by `org-agenda-file-regexp' will be part of the file list.
If the value of the variable is not a list but a single file name, then
-the list of agenda files is actually stored and maintained in that file, one
-agenda file per line. In this file paths can be given relative to
+the list of agenda files is actually stored and maintained in that file,
+one agenda file per line. In this file paths can be given relative to
`org-directory'. Tilde expansion and environment variable substitution
-are also made."
+are also made.
+
+Entries may be added to this list with `\\[org-agenda-file-to-front]'
+and removed with `\\[org-remove-file]'."
:group 'org-agenda
:type '(choice
(repeat :tag "List of files and directories" file)
@@ -3637,7 +3941,8 @@ regular expression will be included."
(defcustom org-agenda-text-search-extra-files nil
"List of extra files to be searched by text search commands.
These files will be searched in addition to the agenda files by the
-commands `org-search-view' (`C-c a s') and `org-occur-in-agenda-files'.
+commands `org-search-view' (`\\[org-agenda] s') \
+and `org-occur-in-agenda-files'.
Note that these files will only be searched for text search commands,
not for the other agenda views like todo lists, tag searches or the weekly
agenda. This variable is intended to list notes and possibly archive files
@@ -3650,7 +3955,7 @@ scope."
(const :tag "Agenda Archives" agenda-archives)
(repeat :inline t (file))))
-(org-defvaralias 'org-agenda-multi-occur-extra-files
+(defvaralias 'org-agenda-multi-occur-extra-files
'org-agenda-text-search-extra-files)
(defcustom org-agenda-skip-unavailable-files nil
@@ -3670,7 +3975,7 @@ forth between agenda and calendar."
(defcustom org-calendar-insert-diary-entry-key [?i]
"The key to be installed in `calendar-mode-map' for adding diary entries.
This option is irrelevant until `org-agenda-diary-file' has been configured
-to point to an Org-mode file. When that is the case, the command
+to point to an Org file. When that is the case, the command
`org-agenda-diary-entry' will be bound to the key given here, by default
`i'. In the calendar, `i' normally adds entries to `diary-file'. So
if you want to continue doing this, you need to change this to a different
@@ -3700,7 +4005,7 @@ points to a file, `org-agenda-diary-entry' will be used instead."
'org-agenda-diary-entry))))))
(defgroup org-latex nil
- "Options for embedding LaTeX code into Org-mode."
+ "Options for embedding LaTeX code into Org mode."
:tag "Org LaTeX"
:group 'org)
@@ -3755,39 +4060,131 @@ Replace format-specifiers in the command as noted below and use
`shell-command' to convert LaTeX to MathML.
%j: Executable file in fully expanded form as specified by
`org-latex-to-mathml-jar-file'.
-%I: Input LaTeX file in fully expanded form
-%o: Output MathML file
+%I: Input LaTeX file in fully expanded form.
+%i: The latex fragment to be converted.
+%o: Output MathML file.
+
This command is used by `org-create-math-formula'.
-When using MathToWeb as the converter, set this to
-\"java -jar %j -unicode -force -df %o %I\"."
+When using MathToWeb as the converter, set this option to
+\"java -jar %j -unicode -force -df %o %I\".
+
+When using LaTeXML set this option to
+\"latexmlmath \"%i\" --presentationmathml=%o\"."
:group 'org-latex
:version "24.1"
:type '(choice
(const :tag "None" nil)
(string :tag "\nShell command")))
-(defcustom org-latex-create-formula-image-program 'dvipng
- "Program to convert LaTeX fragments with.
-
-dvipng Process the LaTeX fragments to dvi file, then convert
- dvi files to png files using dvipng.
- This will also include processing of non-math environments.
-imagemagick Convert the LaTeX fragments to pdf files and use imagemagick
- to convert pdf files to png files"
+(defcustom org-preview-latex-default-process 'dvipng
+ "The default process to convert LaTeX fragments to image files.
+All available processes and theirs documents can be found in
+`org-preview-latex-process-alist', which see."
:group 'org-latex
- :version "24.1"
- :type '(choice
- (const :tag "dvipng" dvipng)
- (const :tag "imagemagick" imagemagick)))
+ :version "26.1"
+ :package-version '(Org . "9.0")
+ :type 'symbol)
+
+(defcustom org-preview-latex-process-alist
+ '((dvipng
+ :programs ("latex" "dvipng")
+ :description "dvi > png"
+ :message "you need to install the programs: latex and dvipng."
+ :image-input-type "dvi"
+ :image-output-type "png"
+ :image-size-adjust (1.0 . 1.0)
+ :latex-compiler ("latex -interaction nonstopmode -output-directory %o %f")
+ :image-converter ("dvipng -fg %F -bg %B -D %D -T tight -o %O %f"))
+ (dvisvgm
+ :programs ("latex" "dvisvgm")
+ :description "dvi > svg"
+ :message "you need to install the programs: latex and dvisvgm."
+ :use-xcolor t
+ :image-input-type "dvi"
+ :image-output-type "svg"
+ :image-size-adjust (1.7 . 1.5)
+ :latex-compiler ("latex -interaction nonstopmode -output-directory %o %f")
+ :image-converter ("dvisvgm %f -n -b min -c %S -o %O"))
+ (imagemagick
+ :programs ("latex" "convert")
+ :description "pdf > png"
+ :message "you need to install the programs: latex and imagemagick."
+ :use-xcolor t
+ :image-input-type "pdf"
+ :image-output-type "png"
+ :image-size-adjust (1.0 . 1.0)
+ :latex-compiler ("pdflatex -interaction nonstopmode -output-directory %o %f")
+ :image-converter
+ ("convert -density %D -trim -antialias %f -quality 100 %O")))
+ "Definitions of external processes for LaTeX previewing.
+Org mode can use some external commands to generate TeX snippet's images for
+previewing or inserting into HTML files, e.g., \"dvipng\". This variable tells
+`org-create-formula-image' how to call them.
+
+The value is an alist with the pattern (NAME . PROPERTIES). NAME is a symbol.
+PROPERTIES accepts the following attributes:
+
+ :programs list of strings, required programs.
+ :description string, describe the process.
+ :message string, message it when required programs cannot be found.
+ :image-input-type string, input file type of image converter (e.g., \"dvi\").
+ :image-output-type string, output file type of image converter (e.g., \"png\").
+ :use-xcolor boolean, when non-nil, LaTeX \"xcolor\" macro is used to
+ deal with background and foreground color of image.
+ Otherwise, dvipng style background and foreground color
+ format are generated. You may then refer to them in
+ command options with \"%F\" and \"%B\".
+ :image-size-adjust cons of numbers, the car element is used to adjust LaTeX
+ image size showed in buffer and the cdr element is for
+ HTML file. This option is only useful for process
+ developers, users should use variable
+ `org-format-latex-options' instead.
+ :post-clean list of strings, files matched are to be cleaned up once
+ the image is generated. When nil, the files with \".dvi\",
+ \".xdv\", \".pdf\", \".tex\", \".aux\", \".log\", \".svg\",
+ \".png\", \".jpg\", \".jpeg\" or \".out\" extension will
+ be cleaned up.
+ :latex-header list of strings, the LaTeX header of the snippet file.
+ When nil, the fallback value is used instead, which is
+ controlled by `org-format-latex-header',
+ `org-latex-default-packages-alist' and
+ `org-latex-packages-alist', which see.
+ :latex-compiler list of LaTeX commands, as strings. Each of them is given
+ to the shell. Place-holders \"%t\", \"%b\" and \"%o\" are
+ replaced with values defined below.
+ :image-converter list of image converter commands strings. Each of them is
+ given to the shell and supports any of the following
+ place-holders defined below.
+
+Place-holders used by `:image-converter' and `:latex-compiler':
+
+ %f input file name
+ %b base name of input file
+ %o base directory of input file
+ %O absolute output file name
+
+Place-holders only used by `:image-converter':
+
+ %F foreground of image
+ %B background of image
+ %D dpi, which is used to adjust image size by some processing commands.
+ %S the image size scale ratio, which is used to adjust image size by some
+ processing commands."
+ :group 'org-latex
+ :version "26.1"
+ :package-version '(Org . "9.0")
+ :type '(alist :tag "LaTeX to image backends"
+ :value-type (plist)))
-(defcustom org-latex-preview-ltxpng-directory "ltxpng/"
+(defcustom org-preview-latex-image-directory "ltximg/"
"Path to store latex preview images.
A relative path here creates many directories relative to the
processed org files paths. An absolute path puts all preview
images at the same place."
:group 'org-latex
- :version "24.3"
+ :version "26.1"
+ :package-version '(Org . "9.0")
:type 'string)
(defun org-format-latex-mathml-available-p ()
@@ -3805,8 +4202,8 @@ images at the same place."
(defcustom org-format-latex-header "\\documentclass{article}
\\usepackage[usenames]{color}
-[PACKAGES]
-[DEFAULT-PACKAGES]
+\[PACKAGES]
+\[DEFAULT-PACKAGES]
\\pagestyle{empty} % do not remove
% The settings below are copied from fullpage.sty
\\setlength{\\textwidth}{\\paperwidth}
@@ -3847,22 +4244,19 @@ header, or they will be appended."
(default-value var)))
(defcustom org-latex-default-packages-alist
- '(("AUTO" "inputenc" t)
- ("T1" "fontenc" t)
- ("" "fixltx2e" nil)
+ '(("AUTO" "inputenc" t ("pdflatex"))
+ ("T1" "fontenc" t ("pdflatex"))
("" "graphicx" t)
+ ("" "grffile" t)
("" "longtable" nil)
- ("" "float" nil)
("" "wrapfig" nil)
("" "rotating" nil)
("normalem" "ulem" t)
("" "amsmath" t)
("" "textcomp" t)
- ("" "marvosym" t)
- ("" "wasysym" t)
("" "amssymb" t)
- ("" "hyperref" nil)
- "\\tolerance=1000")
+ ("" "capt-of" nil)
+ ("" "hyperref" nil))
"Alist of default packages to be inserted in the header.
Change this only if one of the packages here causes an
@@ -3872,16 +4266,17 @@ The packages in this list are needed by one part or another of
Org mode to function properly:
- inputenc, fontenc: for basic font and character selection
-- fixltx2e: Important patches of LaTeX itself
- graphicx: for including images
+- grffile: allow periods and spaces in graphics file names
- longtable: For multipage tables
-- float, wrapfig: for figure placement
+- wrapfig: for figure placement
- rotating: for sideways figures and tables
- ulem: for underline and strike-through
- amsmath: for subscript and superscript and math environments
-- textcomp, marvosymb, wasysym, amssymb: for various symbols used
+- textcomp, amssymb: for various symbols used
for interpreting the entities in `org-entities'. You can skip
some of these packages if you don't use any of their symbols.
+- capt-of: for captions outside of floats
- hyperref: for cross references
Therefore you should not modify this variable unless you know
@@ -3890,20 +4285,24 @@ you might be loading some other package that conflicts with one
of the default packages. Each element is either a cell or
a string.
-A cell is of the format:
+A cell is of the format
- ( \"options\" \"package\" SNIPPET-FLAG).
+ (\"options\" \"package\" SNIPPET-FLAG COMPILERS)
If SNIPPET-FLAG is non-nil, the package also needs to be included
when compiling LaTeX snippets into images for inclusion into
-non-LaTeX output.
+non-LaTeX output. COMPILERS is a list of compilers that should
+include the package, see `org-latex-compiler'. If the document
+compiler is not in the list, and the list is non-nil, the package
+will not be inserted in the final document.
A string will be inserted as-is in the header of the document."
:group 'org-latex
:group 'org-export-latex
:set 'org-set-packages-alist
:get 'org-get-packages-alist
- :version "24.1"
+ :version "26.1"
+ :package-version '(Org . "8.3")
:type '(repeat
(choice
(list :tag "options/package pair"
@@ -3947,7 +4346,7 @@ Make sure that you only list packages here which:
(string :tag "A line of LaTeX"))))
(defgroup org-appearance nil
- "Settings for Org-mode appearance."
+ "Settings for Org mode appearance."
:tag "Org Appearance"
:group 'org)
@@ -4038,6 +4437,11 @@ following symbols:
:group 'org-appearance
:type 'boolean)
+(defcustom org-hide-macro-markers nil
+ "Non-nil mean font-lock should hide the brackets marking macro calls."
+ :group 'org-appearance
+ :type 'boolean)
+
(defcustom org-pretty-entities nil
"Non-nil means show entities as UTF8 characters.
When nil, the \\name form remains in the buffer."
@@ -4124,7 +4528,7 @@ After a match, the match groups contain these elements:
;; set this option proved cumbersome. See this message/thread:
;; http://article.gmane.org/gmane.emacs.orgmode/68681
(defvar org-emphasis-regexp-components
- '(" \t('\"{" "- \t.,:!?;'\")}\\" " \t\r\n,\"'" "." 1)
+ '(" \t('\"{" "- \t.,:!?;'\")}\\[" " \t\r\n" "." 1)
"Components used to build the regular expression for emphasis.
This is a list with five entries. Terminology: In an emphasis string
like \" *strong word* \", we call the initial space PREMATCH, the final
@@ -4142,17 +4546,17 @@ newline The maximum number of newlines allowed in an emphasis exp.
You need to reload Org or to restart Emacs after customizing this.")
(defcustom org-emphasis-alist
- `(("*" bold)
+ '(("*" bold)
("/" italic)
("_" underline)
("=" org-verbatim verbatim)
("~" org-code verbatim)
- ("+" ,(if (featurep 'xemacs) 'org-table '(:strike-through t))))
+ ("+" (:strike-through t)))
"Alist of characters and faces to emphasize text.
Text starting and ending with a special character will be emphasized,
for example *bold*, _underlined_ and /italic/. This variable sets the
marker characters and the face to be used by font-lock for highlighting
-in Org-mode Emacs buffers.
+in Org buffers.
You need to reload Org or to restart Emacs after customizing this."
:group 'org-appearance
@@ -4167,122 +4571,68 @@ You need to reload Org or to restart Emacs after customizing this."
(plist :tag "Face property list"))
(option (const verbatim)))))
-(defvar org-protecting-blocks
- '("src" "example" "latex" "ascii" "html" "ditaa" "dot" "r" "R")
+(defvar org-protecting-blocks '("src" "example" "export")
"Blocks that contain text that is quoted, i.e. not processed as Org syntax.
This is needed for font-lock setup.")
-;;; Miscellaneous options
-
-(defgroup org-completion nil
- "Completion in Org-mode."
- :tag "Org Completion"
- :group 'org)
-
-(defcustom org-completion-use-ido nil
- "Non-nil means use ido completion wherever possible.
-Note that `ido-mode' must be active for this variable to be relevant.
-If you decide to turn this variable on, you might well want to turn off
-`org-outline-path-complete-in-steps'.
-See also `org-completion-use-iswitchb'."
- :group 'org-completion
- :type 'boolean)
-
-(defcustom org-completion-use-iswitchb nil
- "Non-nil means use iswitchb completion wherever possible.
-Note that `iswitchb-mode' must be active for this variable to be relevant.
-If you decide to turn this variable on, you might well want to turn off
-`org-outline-path-complete-in-steps'.
-Note that this variable has only an effect if `org-completion-use-ido' is nil."
- :group 'org-completion
- :type 'boolean)
-
-(defcustom org-completion-fallback-command 'hippie-expand
- "The expansion command called by \\[pcomplete] in normal context.
-Normal means, no org-mode-specific context."
- :group 'org-completion
- :type 'function)
-
;;; Functions and variables from their packages
;; Declared here to avoid compiler warnings
-
-;; XEmacs only
-(defvar outline-mode-menu-heading)
-(defvar outline-mode-menu-show)
-(defvar outline-mode-menu-hide)
-(defvar zmacs-regions) ; XEmacs regions
-
-;; Emacs only
(defvar mark-active)
;; Various packages
-(declare-function calendar-iso-to-absolute "cal-iso" (date))
-(declare-function calendar-forward-day "cal-move" (arg))
-(declare-function calendar-goto-date "cal-move" (date))
-(declare-function calendar-goto-today "cal-move" ())
-(declare-function calendar-iso-from-absolute "cal-iso" (date))
-(defvar calc-embedded-close-formula)
-(defvar calc-embedded-open-formula)
-(declare-function cdlatex-tab "ext:cdlatex" ())
+(declare-function calc-eval "calc" (str &optional separator &rest args))
+(declare-function calendar-forward-day "cal-move" (arg))
+(declare-function calendar-goto-date "cal-move" (date))
+(declare-function calendar-goto-today "cal-move" ())
+(declare-function calendar-iso-from-absolute "cal-iso" (date))
+(declare-function calendar-iso-to-absolute "cal-iso" (date))
(declare-function cdlatex-compute-tables "ext:cdlatex" ())
-(declare-function dired-get-filename "dired" (&optional localp no-error-if-not-filep))
-(defvar font-lock-unfontify-region-function)
-(declare-function iswitchb-read-buffer "iswitchb"
- (prompt &optional
- default require-match _predicate start matches-set))
-(defvar iswitchb-temp-buflist)
-(declare-function org-gnus-follow-link "org-gnus" (&optional group article))
-(defvar org-agenda-tags-todo-honor-ignore-options)
-(declare-function org-agenda-skip "org-agenda" ())
-(declare-function
- org-agenda-format-item "org-agenda"
- (extra txt &optional level category tags dotime remove-re habitp))
-(declare-function org-agenda-new-marker "org-agenda" (&optional pos))
-(declare-function org-agenda-change-all-lines "org-agenda"
+(declare-function cdlatex-tab "ext:cdlatex" ())
+(declare-function dired-get-filename
+ "dired"
+ (&optional localp no-error-if-not-filep))
+(declare-function iswitchb-read-buffer
+ "iswitchb"
+ (prompt &optional
+ default require-match _predicate start matches-set))
+(declare-function org-agenda-change-all-lines
+ "org-agenda"
(newhead hdmarker &optional fixface just-this))
-(declare-function org-agenda-set-restriction-lock "org-agenda" (&optional type))
+(declare-function org-agenda-check-for-timestamp-as-reason-to-ignore-todo-item
+ "org-agenda"
+ (&optional end))
+(declare-function org-agenda-copy-local-variable "org-agenda" (var))
+(declare-function org-agenda-format-item
+ "org-agenda"
+ (extra txt &optional level category tags dotime
+ remove-re habitp))
(declare-function org-agenda-maybe-redo "org-agenda" ())
-(declare-function org-agenda-save-markers-for-cut-and-paste "org-agenda"
+(declare-function org-agenda-new-marker "org-agenda" (&optional pos))
+(declare-function org-agenda-save-markers-for-cut-and-paste
+ "org-agenda"
(beg end))
-(declare-function org-agenda-copy-local-variable "org-agenda" (var))
-(declare-function org-agenda-check-for-timestamp-as-reason-to-ignore-todo-item
- "org-agenda" (&optional end))
-(declare-function org-inlinetask-remove-END-maybe "org-inlinetask" ())
-(declare-function org-inlinetask-in-task-p "org-inlinetask" ())
+(declare-function org-agenda-set-restriction-lock "org-agenda" (&optional type))
+(declare-function org-agenda-skip "org-agenda" ())
+(declare-function org-attach-reveal "org-attach" (&optional if-exists))
+(declare-function org-gnus-follow-link "org-gnus" (&optional group article))
+(declare-function org-indent-mode "org-indent" (&optional arg))
(declare-function org-inlinetask-goto-beginning "org-inlinetask" ())
(declare-function org-inlinetask-goto-end "org-inlinetask" ())
-(declare-function org-indent-mode "org-indent" (&optional arg))
-(declare-function parse-time-string "parse-time" (string))
-(declare-function org-attach-reveal "org-attach" (&optional if-exists))
+(declare-function org-inlinetask-in-task-p "org-inlinetask" ())
+(declare-function org-inlinetask-remove-END-maybe "org-inlinetask" ())
(declare-function orgtbl-send-table "org-table" (&optional maybe))
-(defvar remember-data-file)
-(defvar texmathp-why)
+(declare-function parse-time-string "parse-time" (string))
(declare-function speedbar-line-directory "speedbar" (&optional depth))
-(declare-function table--at-cell-p "table" (position &optional object at-column))
-
-(defvar org-latex-regexps)
-
-;;; Autoload and prepare some org modules
-
-;; Some table stuff that needs to be defined here, because it is used
-;; by the functions setting up org-mode or checking for table context.
-
-(defconst org-table-any-line-regexp "^[ \t]*\\(|\\|\\+-[-+]\\)"
- "Detect an org-type or table-type table.")
-(defconst org-table-line-regexp "^[ \t]*|"
- "Detect an org-type table line.")
-(defconst org-table-dataline-regexp "^[ \t]*|[^-]"
- "Detect an org-type table line.")
-(defconst org-table-hline-regexp "^[ \t]*|-"
- "Detect an org-type table hline.")
-(defconst org-table1-hline-regexp "^[ \t]*\\+-[-+]"
- "Detect a table-type table hline.")
-(defconst org-table-any-border-regexp "^[ \t]*[^|+ \t]"
- "Detect the first line outside a table when searching from within it.
-This works for both table types.")
-(defconst org-TBLFM-regexp "^[ \t]*#\\+TBLFM: "
- "Detect a #+TBLFM line.")
+(defvar align-mode-rules-list)
+(defvar calc-embedded-close-formula)
+(defvar calc-embedded-open-formula)
+(defvar calc-embedded-open-mode)
+(defvar font-lock-unfontify-region-function)
+(defvar iswitchb-temp-buflist)
+(defvar org-agenda-tags-todo-honor-ignore-options)
+(defvar remember-data-file)
+(defvar texmathp-why)
;;;###autoload
(defun turn-on-orgtbl ()
@@ -4291,75 +4641,50 @@ This works for both table types.")
(orgtbl-mode 1))
(defun org-at-table-p (&optional table-type)
- "Return t if the cursor is inside an org-type table.
-If TABLE-TYPE is non-nil, also check for table.el-type tables."
- (if org-enable-table-editor
- (save-excursion
- (beginning-of-line 1)
- (looking-at (if table-type org-table-any-line-regexp
- org-table-line-regexp)))
- nil))
-(defsubst org-table-p () (org-at-table-p))
+ "Non-nil if the cursor is inside an Org table.
+If TABLE-TYPE is non-nil, also check for table.el-type tables.
+If `org-enable-table-editor' is nil, return nil unconditionally."
+ (and
+ org-enable-table-editor
+ (save-excursion
+ (beginning-of-line)
+ (looking-at-p (if table-type "[ \t]*[|+]" "[ \t]*|")))
+ (or (not (derived-mode-p 'org-mode))
+ (let ((e (org-element-lineage (org-element-at-point) '(table) t)))
+ (and e (or table-type (eq (org-element-property :type e) 'org)))))))
(defun org-at-table.el-p ()
- "Return t if and only if we are at a table.el table."
- (and (org-at-table-p 'any)
- (save-excursion
- (goto-char (org-table-begin 'any))
- (looking-at org-table1-hline-regexp))))
-
-(defun org-table-recognize-table.el ()
- "If there is a table.el table nearby, recognize it and move into it."
- (if org-table-tab-recognizes-table.el
- (if (org-at-table.el-p)
- (progn
- (beginning-of-line 1)
- (if (looking-at org-table-dataline-regexp)
- nil
- (if (looking-at org-table1-hline-regexp)
- (progn
- (beginning-of-line 2)
- (if (looking-at org-table-any-border-regexp)
- (beginning-of-line -1)))))
- (if (re-search-forward "|" (org-table-end t) t)
- (progn
- (require 'table)
- (if (table--at-cell-p (point))
- t
- (message "recognizing table.el table...")
- (table-recognize-table)
- (message "recognizing table.el table...done")))
- (error "This should not happen"))
- t)
- nil)
- nil))
+ "Non-nil when point is at a table.el table."
+ (and (save-excursion (beginning-of-line) (looking-at "[ \t]*[|+]"))
+ (let ((element (org-element-at-point)))
+ (and (eq (org-element-type element) 'table)
+ (eq (org-element-property :type element) 'table.el)))))
(defun org-at-table-hline-p ()
- "Return t if the cursor is inside a hline in a table."
- (if org-enable-table-editor
- (save-excursion
- (beginning-of-line 1)
- (looking-at org-table-hline-regexp))
- nil))
+ "Non-nil when point is inside a hline in a table.
+Assume point is already in a table. If `org-enable-table-editor'
+is nil, return nil unconditionally."
+ (and org-enable-table-editor
+ (save-excursion
+ (beginning-of-line)
+ (looking-at org-table-hline-regexp))))
(defun org-table-map-tables (function &optional quietly)
"Apply FUNCTION to the start of all tables in the buffer."
- (save-excursion
- (save-restriction
- (widen)
- (goto-char (point-min))
- (while (re-search-forward org-table-any-line-regexp nil t)
- (unless quietly
- (message "Mapping tables: %d%%"
- (floor (* 100.0 (point)) (buffer-size))))
- (beginning-of-line 1)
- (when (and (looking-at org-table-line-regexp)
- ;; Exclude tables in src/example/verbatim/clocktable blocks
- (not (org-in-block-p '("src" "example" "verbatim" "clocktable"))))
- (save-excursion (funcall function))
- (or (looking-at org-table-line-regexp)
- (forward-char 1)))
- (re-search-forward org-table-any-border-regexp nil 1))))
+ (org-with-wide-buffer
+ (goto-char (point-min))
+ (while (re-search-forward org-table-any-line-regexp nil t)
+ (unless quietly
+ (message "Mapping tables: %d%%"
+ (floor (* 100.0 (point)) (buffer-size))))
+ (beginning-of-line 1)
+ (when (and (looking-at org-table-line-regexp)
+ ;; Exclude tables in src/example/verbatim/clocktable blocks
+ (not (org-in-block-p '("src" "example" "verbatim" "clocktable"))))
+ (save-excursion (funcall function))
+ (or (looking-at org-table-line-regexp)
+ (forward-char 1)))
+ (re-search-forward org-table-any-border-regexp nil 1)))
(unless quietly (message "Mapping tables: done")))
(declare-function org-clock-save-markers-for-cut-and-paste "org-clock" (beg end))
@@ -4368,12 +4693,12 @@ If TABLE-TYPE is non-nil, also check for table.el-type tables."
(&optional also-non-dangling-p prompt last-valid))
(defun org-at-TBLFM-p (&optional pos)
- "Return t when point (or POS) is in #+TBLFM line."
+ "Non-nil when point (or POS) is in #+TBLFM line."
(save-excursion
- (let ((pos pos)))
(goto-char (or pos (point)))
- (beginning-of-line 1)
- (looking-at org-TBLFM-regexp)))
+ (beginning-of-line)
+ (and (let ((case-fold-search t)) (looking-at org-TBLFM-regexp))
+ (eq (org-element-type (org-element-at-point)) 'table))))
(defvar org-clock-start-time)
(defvar org-clock-marker (make-marker)
@@ -4410,7 +4735,7 @@ If yes, offer to stop it and to save the buffer with the changes."
(add-hook 'kill-emacs-hook 'org-clock-save))
(defgroup org-archive nil
- "Options concerning archiving in Org-mode."
+ "Options concerning archiving in Org mode."
:tag "Org Archive"
:group 'org-structure)
@@ -4425,7 +4750,7 @@ When the filename is omitted, archiving happens in the same file.
%s in the filename will be replaced by the current file
name (without the directory part). Archiving to a different file
is useful to keep archived entries from contributing to the
-Org-mode Agenda.
+Org Agenda.
The archived entries will be filed as subtrees of the specified
headline. When the headline is omitted, the subtrees are simply
@@ -4473,16 +4798,6 @@ the hierarchy, it will be used."
:group 'org-archive
:type 'string)
-(defcustom org-archive-tag "ARCHIVE"
- "The tag that marks a subtree as archived.
-An archived subtree does not open during visibility cycling, and does
-not contribute to the agenda listings.
-After changing this, font-lock must be restarted in the relevant buffers to
-get the proper fontification."
- :group 'org-archive
- :group 'org-keywords
- :type 'string)
-
(defcustom org-agenda-skip-archived-trees t
"Non-nil means the agenda will skip any items located in archived trees.
An archived tree is a tree marked with the tag ARCHIVE. The use of this
@@ -4515,24 +4830,25 @@ collapsed state."
:group 'org-sparse-trees
:type 'boolean)
-(defcustom org-sparse-tree-default-date-type 'scheduled-or-deadline
+(defcustom org-sparse-tree-default-date-type nil
"The default date type when building a sparse tree.
When this is nil, a date is a scheduled or a deadline timestamp.
Otherwise, these types are allowed:
all: all timestamps
active: only active timestamps (<...>)
- inactive: only inactive timestamps (<...)
+ inactive: only inactive timestamps ([...])
scheduled: only scheduled timestamps
deadline: only deadline timestamps"
- :type '(choice (const :tag "Scheduled or deadline" scheduled-or-deadline)
+ :type '(choice (const :tag "Scheduled or deadline" nil)
(const :tag "All timestamps" all)
(const :tag "Only active timestamps" active)
(const :tag "Only inactive timestamps" inactive)
(const :tag "Only scheduled timestamps" scheduled)
(const :tag "Only deadline timestamps" deadline)
(const :tag "Only closed timestamps" closed))
- :version "24.3"
+ :version "26.1"
+ :package-version '(Org . "8.3")
:group 'org-sparse-trees)
(defun org-cycle-hide-archived-subtrees (state)
@@ -4545,9 +4861,10 @@ Otherwise, these types are allowed:
(end (if globalp (point-max) (org-end-of-subtree t))))
(org-hide-archived-subtrees beg end)
(goto-char beg)
- (if (looking-at (concat ".*:" org-archive-tag ":"))
- (message "%s" (substitute-command-keys
- "Subtree is archived and stays closed. Use \\[org-force-cycle-archived] to cycle it anyway.")))))))
+ (when (looking-at-p (concat ".*:" org-archive-tag ":"))
+ (message "%s" (substitute-command-keys
+ "Subtree is archived and stays closed. Use \
+`\\[org-force-cycle-archived]' to cycle it anyway.")))))))
(defun org-force-cycle-archived ()
"Cycle subtree even if it is archived."
@@ -4558,13 +4875,16 @@ Otherwise, these types are allowed:
(defun org-hide-archived-subtrees (beg end)
"Re-hide all archived subtrees after a visibility state change."
- (save-excursion
- (let* ((re (concat ":" org-archive-tag ":")))
- (goto-char beg)
- (while (re-search-forward re end t)
- (when (org-at-heading-p)
- (org-flag-subtree t)
- (org-end-of-subtree t))))))
+ (org-with-wide-buffer
+ (let ((case-fold-search nil)
+ (re (concat org-outline-regexp-bol ".*:" org-archive-tag ":")))
+ (goto-char beg)
+ ;; Include headline point is currently on.
+ (beginning-of-line)
+ (while (and (< (point) end) (re-search-forward re end t))
+ (when (member org-archive-tag (org-get-tags))
+ (org-flag-subtree t)
+ (org-end-of-subtree t))))))
(declare-function outline-end-of-heading "outline" ())
(declare-function outline-flag-region "outline" (from to flag))
@@ -4580,7 +4900,6 @@ Otherwise, these types are allowed:
;; Declare Column View Code
-(declare-function org-columns-number-to-string "org-colview" (n fmt &optional printf))
(declare-function org-columns-get-format-and-top-level "org-colview" ())
(declare-function org-columns-compute "org-colview" (property))
@@ -4593,79 +4912,47 @@ Otherwise, these types are allowed:
;;; Variables for pre-computed regular expressions, all buffer local
-(defvar org-drawer-regexp "^[ \t]*:PROPERTIES:[ \t]*$"
- "Matches first line of a hidden block.")
-(make-variable-buffer-local 'org-drawer-regexp)
-(defvar org-todo-regexp nil
- "Matches any of the TODO state keywords.")
-(make-variable-buffer-local 'org-todo-regexp)
-(defvar org-not-done-regexp nil
- "Matches any of the TODO state keywords except the last one.")
-(make-variable-buffer-local 'org-not-done-regexp)
-(defvar org-not-done-heading-regexp nil
- "Matches a TODO headline that is not done.")
-(make-variable-buffer-local 'org-not-done-regexp)
-(defvar org-todo-line-regexp nil
- "Matches a headline and puts TODO state into group 2 if present.")
-(make-variable-buffer-local 'org-todo-line-regexp)
-(defvar org-complex-heading-regexp nil
+(defvar-local org-todo-regexp nil
+ "Matches any of the TODO state keywords.
+Since TODO keywords are case-sensitive, `case-fold-search' is
+expected to be bound to nil when matching against this regexp.")
+
+(defvar-local org-not-done-regexp nil
+ "Matches any of the TODO state keywords except the last one.
+Since TODO keywords are case-sensitive, `case-fold-search' is
+expected to be bound to nil when matching against this regexp.")
+
+(defvar-local org-not-done-heading-regexp nil
+ "Matches a TODO headline that is not done.
+Since TODO keywords are case-sensitive, `case-fold-search' is
+expected to be bound to nil when matching against this regexp.")
+
+(defvar-local org-todo-line-regexp nil
+ "Matches a headline and puts TODO state into group 2 if present.
+Since TODO keywords are case-sensitive, `case-fold-search' is
+expected to be bound to nil when matching against this regexp.")
+
+(defvar-local org-complex-heading-regexp nil
"Matches a headline and puts everything into groups:
-group 1: the stars
-group 2: The todo keyword, maybe
+
+group 1: Stars
+group 2: The TODO keyword, maybe
group 3: Priority cookie
group 4: True headline
-group 5: Tags")
-(make-variable-buffer-local 'org-complex-heading-regexp)
-(defvar org-complex-heading-regexp-format nil
+group 5: Tags
+
+Since TODO keywords are case-sensitive, `case-fold-search' is
+expected to be bound to nil when matching against this regexp.")
+
+(defvar-local org-complex-heading-regexp-format nil
"Printf format to make regexp to match an exact headline.
This regexp will match the headline of any node which has the
exact headline text that is put into the format, but may have any
TODO state, priority and tags.")
-(make-variable-buffer-local 'org-complex-heading-regexp-format)
-(defvar org-todo-line-tags-regexp nil
+
+(defvar-local org-todo-line-tags-regexp nil
"Matches a headline and puts TODO state into group 2 if present.
Also put tags into group 4 if tags are present.")
-(make-variable-buffer-local 'org-todo-line-tags-regexp)
-(defvar org-ds-keyword-length 12
- "Maximum length of the DEADLINE and SCHEDULED keywords.")
-(make-variable-buffer-local 'org-ds-keyword-length)
-(defvar org-deadline-regexp nil
- "Matches the DEADLINE keyword.")
-(make-variable-buffer-local 'org-deadline-regexp)
-(defvar org-deadline-time-regexp nil
- "Matches the DEADLINE keyword together with a time stamp.")
-(make-variable-buffer-local 'org-deadline-time-regexp)
-(defvar org-deadline-time-hour-regexp nil
- "Matches the DEADLINE keyword together with a time-and-hour stamp.")
-(make-variable-buffer-local 'org-deadline-time-hour-regexp)
-(defvar org-deadline-line-regexp nil
- "Matches the DEADLINE keyword and the rest of the line.")
-(make-variable-buffer-local 'org-deadline-line-regexp)
-(defvar org-scheduled-regexp nil
- "Matches the SCHEDULED keyword.")
-(make-variable-buffer-local 'org-scheduled-regexp)
-(defvar org-scheduled-time-regexp nil
- "Matches the SCHEDULED keyword together with a time stamp.")
-(make-variable-buffer-local 'org-scheduled-time-regexp)
-(defvar org-scheduled-time-hour-regexp nil
- "Matches the SCHEDULED keyword together with a time-and-hour stamp.")
-(make-variable-buffer-local 'org-scheduled-time-hour-regexp)
-(defvar org-closed-time-regexp nil
- "Matches the CLOSED keyword together with a time stamp.")
-(make-variable-buffer-local 'org-closed-time-regexp)
-
-(defvar org-keyword-time-regexp nil
- "Matches any of the 4 keywords, together with the time stamp.")
-(make-variable-buffer-local 'org-keyword-time-regexp)
-(defvar org-keyword-time-not-clock-regexp nil
- "Matches any of the 3 keywords, together with the time stamp.")
-(make-variable-buffer-local 'org-keyword-time-not-clock-regexp)
-(defvar org-maybe-keyword-time-regexp nil
- "Matches a timestamp, possibly preceded by a keyword.")
-(make-variable-buffer-local 'org-maybe-keyword-time-regexp)
-(defvar org-all-time-keywords nil
- "List of time keywords.")
-(make-variable-buffer-local 'org-all-time-keywords)
(defconst org-plain-time-of-day-regexp
(concat
@@ -4771,32 +5058,6 @@ in the #+STARTUP line), the corresponding variable, and the value to set
this variable to if the option is found. An optional forth element PUSH
means to push this value onto the list in the variable.")
-(defun org-update-property-plist (key val props)
- "Update PROPS with KEY and VAL."
- (let* ((appending (string= "+" (substring key (- (length key) 1))))
- (key (if appending (substring key 0 (- (length key) 1)) key))
- (remainder (org-remove-if (lambda (p) (string= (car p) key)) props))
- (previous (cdr (assoc key props))))
- (if appending
- (cons (cons key (if previous (concat previous " " val) val)) remainder)
- (cons (cons key val) remainder))))
-
-(defconst org-block-regexp
- "^[ \t]*#\\+begin_?\\([^ \n]+\\)\\(\\([^\n]+\\)\\)?\n\\([^\000]+?\\)#\\+end_?\\1[ \t]*$"
- "Regular expression for hiding blocks.")
-(defconst org-heading-keyword-regexp-format
- "^\\(\\*+\\)\\(?: +%s\\)\\(?: +\\(.*?\\)\\)?[ \t]*$"
- "Printf format for a regexp matching a headline with some keyword.
-This regexp will match the headline of any node which has the
-exact keyword that is put into the format. The keyword isn't in
-any group by default, but the stars and the body are.")
-(defconst org-heading-keyword-maybe-regexp-format
- "^\\(\\*+\\)\\(?: +%s\\)?\\(?: +\\(.*?\\)\\)?[ \t]*$"
- "Printf format for a regexp matching a headline, possibly with some keyword.
-This regexp can match any headline with the specified keyword, or
-without a keyword. The keyword isn't in any group by default,
-but the stars and the body are.")
-
(defcustom org-group-tags t
"When non-nil (the default), use group tags.
This can be turned on/off through `org-toggle-tags-groups'."
@@ -4820,386 +5081,378 @@ Support for group tags is controlled by the option
(message "Groups tags support has been turned %s"
(if org-group-tags "on" "off")))
-(defun org-set-regexps-and-options-for-tags ()
- "Precompute variables used for tags."
- (when (derived-mode-p 'org-mode)
- (org-set-local 'org-file-tags nil)
- (let ((re (org-make-options-regexp '("FILETAGS" "TAGS")))
- (splitre "[ \t]+")
- (start 0)
- tags ftags key value)
- (save-excursion
- (save-restriction
- (widen)
- (goto-char (point-min))
- (while (re-search-forward re nil t)
- (setq key (upcase (org-match-string-no-properties 1))
- value (org-match-string-no-properties 2))
- (if (stringp value) (setq value (org-trim value)))
- (cond
- ((equal key "TAGS")
- (setq tags (append tags (if tags '("\\n") nil)
- (org-split-string value splitre))))
- ((equal key "FILETAGS")
- (when (string-match "\\S-" value)
- (setq ftags
- (append
- ftags
- (apply 'append
- (mapcar (lambda (x) (org-split-string x ":"))
- (org-split-string value)))))))))))
- ;; Process the file tags.
- (and ftags (org-set-local 'org-file-tags
- (mapcar 'org-add-prop-inherited ftags)))
- (org-set-local 'org-tag-groups-alist nil)
- ;; Process the tags.
- (when (and (not tags) org-tag-alist)
- (setq tags
- (mapcar
- (lambda (tg) (cond ((eq (car tg) :startgroup) "{")
- ((eq (car tg) :endgroup) "}")
- ((eq (car tg) :grouptags) ":")
- ((eq (car tg) :newline) "\n")
- (t (concat (car tg)
- (if (characterp (cdr tg))
- (format "(%s)" (char-to-string (cdr tg))) "")))))
- org-tag-alist)))
- (let (tgs g)
- (dolist (e tags)
- (cond
- ((equal e "{")
- (progn (push '(:startgroup) tgs)
- (when (equal (nth 1 tags) ":")
- (push (list (replace-regexp-in-string
- "(.+)$" "" (nth 0 tags)))
- org-tag-groups-alist)
- (setq g 0))))
- ((equal e ":") (push '(:grouptags) tgs))
- ((equal e "}") (push '(:endgroup) tgs) (if g (setq g nil)))
- ((equal e "\\n") (push '(:newline) tgs))
- ((string-match (org-re "^\\([[:alnum:]_@#%]+\\)(\\(.\\))$") e)
- (push (cons (match-string 1 e)
- (string-to-char (match-string 2 e)))
- tgs)
- (if (and g (> g 0))
- (setcar org-tag-groups-alist
- (append (car org-tag-groups-alist)
- (list (match-string 1 e)))))
- (if g (setq g (1+ g))))
- (t (push (list e) tgs)
- (if (and g (> g 0))
- (setcar org-tag-groups-alist
- (append (car org-tag-groups-alist) (list e))))
- (if g (setq g (1+ g))))))
- (org-set-local 'org-tag-alist nil)
- (dolist (e tgs)
- (or (and (stringp (car e))
- (assoc (car e) org-tag-alist))
- (push e org-tag-alist)))
- ;; Return a list with tag variables
- (list org-file-tags org-tag-alist org-tag-groups-alist)))))
-
-(defvar org-ota nil)
-(defun org-set-regexps-and-options ()
- "Precompute regular expressions used in the current buffer."
+(defun org-set-regexps-and-options (&optional tags-only)
+ "Precompute regular expressions used in the current buffer.
+When optional argument TAGS-ONLY is non-nil, only compute tags
+related expressions."
(when (derived-mode-p 'org-mode)
- (org-set-local 'org-todo-kwd-alist nil)
- (org-set-local 'org-todo-key-alist nil)
- (org-set-local 'org-todo-key-trigger nil)
- (org-set-local 'org-todo-keywords-1 nil)
- (org-set-local 'org-done-keywords nil)
- (org-set-local 'org-todo-heads nil)
- (org-set-local 'org-todo-sets nil)
- (org-set-local 'org-todo-log-states nil)
- (org-set-local 'org-file-properties nil)
- (let ((re (org-make-options-regexp
- '("CATEGORY" "TODO" "COLUMNS" "STARTUP" "ARCHIVE"
- "LINK" "PRIORITIES" "CONSTANTS" "PROPERTY" "DRAWERS"
- "SETUPFILE" "OPTIONS")
- "\\(?:[a-zA-Z][0-9a-zA-Z_]*_TODO\\)"))
- (splitre "[ \t]+")
- (scripts org-use-sub-superscripts)
- kwds kws0 kwsa key log value cat arch const links hw dws
- tail sep kws1 prio props drawers ext-setup-or-nil setup-contents
- (start 0))
- (save-excursion
- (save-restriction
- (widen)
- (goto-char (point-min))
- (while
- (or (and
- ext-setup-or-nil
- (not org-ota)
- (let (ret)
- (with-temp-buffer
- (insert ext-setup-or-nil)
- (let ((major-mode 'org-mode) org-ota)
- (setq ret (save-match-data
- (org-set-regexps-and-options-for-tags)))))
- ;; Append setupfile tags to existing tags
- (setq org-ota t)
- (setq org-file-tags
- (delq nil (append org-file-tags (nth 0 ret)))
- org-tag-alist
- (delq nil (append org-tag-alist (nth 1 ret)))
- org-tag-groups-alist
- (delq nil (append org-tag-groups-alist (nth 2 ret))))))
- (and ext-setup-or-nil
- (string-match re ext-setup-or-nil start)
- (setq start (match-end 0)))
- (and (setq ext-setup-or-nil nil start 0)
- (re-search-forward re nil t)))
- (setq key (upcase (match-string 1 ext-setup-or-nil))
- value (org-match-string-no-properties 2 ext-setup-or-nil))
- (if (stringp value) (setq value (org-trim value)))
- (cond
- ((equal key "CATEGORY")
- (setq cat value))
- ((member key '("SEQ_TODO" "TODO"))
- (push (cons 'sequence (org-split-string value splitre)) kwds))
- ((equal key "TYP_TODO")
- (push (cons 'type (org-split-string value splitre)) kwds))
- ((string-match "\\`\\([a-zA-Z][0-9a-zA-Z_]*\\)_TODO\\'" key)
- ;; general TODO-like setup
- (push (cons (intern (downcase (match-string 1 key)))
- (org-split-string value splitre))
- kwds))
- ((equal key "COLUMNS")
- (org-set-local 'org-columns-default-format value))
- ((equal key "LINK")
- (when (string-match "^\\(\\S-+\\)[ \t]+\\(.+\\)" value)
- (push (cons (match-string 1 value)
- (org-trim (match-string 2 value)))
- links)))
- ((equal key "PRIORITIES")
- (setq prio (org-split-string value " +")))
- ((equal key "PROPERTY")
- (when (string-match "\\(\\S-+\\)\\s-+\\(.*\\)" value)
- (setq props (org-update-property-plist (match-string 1 value)
- (match-string 2 value)
- props))))
- ((equal key "DRAWERS")
- (setq drawers (delete-dups (append org-drawers (org-split-string value splitre)))))
- ((equal key "CONSTANTS")
- (org-table-set-constants))
- ((equal key "STARTUP")
- (let ((opts (org-split-string value splitre))
- var val)
- (dolist (l opts)
- (when (setq l (assoc l org-startup-options))
- (setq var (nth 1 l) val (nth 2 l))
- (if (not (nth 3 l))
- (set (make-local-variable var) val)
- (if (not (listp (symbol-value var)))
- (set (make-local-variable var) nil))
- (set (make-local-variable var) (symbol-value var))
- (add-to-list var val))))))
- ((equal key "ARCHIVE")
- (setq arch value)
- (remove-text-properties 0 (length arch)
- '(face t fontified t) arch))
- ((equal key "OPTIONS")
- (if (string-match "\\([ \t]\\|\\`\\)\\^:\\(t\\|nil\\|{}\\)" value)
- (setq scripts (read (match-string 2 value)))))
- ((and (equal key "SETUPFILE")
- ;; Prevent checking in Gnus messages
- (not buffer-read-only))
- (setq setup-contents (org-file-contents
- (expand-file-name
- (org-remove-double-quotes value))
- 'noerror))
- (if (not ext-setup-or-nil)
- (setq ext-setup-or-nil setup-contents start 0)
- (setq ext-setup-or-nil
- (concat (substring ext-setup-or-nil 0 start)
- "\n" setup-contents "\n"
- (substring ext-setup-or-nil start)))))))
- ;; search for property blocks
- (goto-char (point-min))
- (while (re-search-forward org-block-regexp nil t)
- (when (equal "PROPERTY" (upcase (match-string 1)))
- (setq value (replace-regexp-in-string
- "[\n\r]" " " (match-string 4)))
- (when (string-match "\\(\\S-+\\)\\s-+\\(.*\\)" value)
- (setq props (org-update-property-plist (match-string 1 value)
- (match-string 2 value)
- props)))))))
- (org-set-local 'org-use-sub-superscripts scripts)
- (when cat
- (org-set-local 'org-category (intern cat))
- (push (cons "CATEGORY" cat) props))
- (when prio
- (if (< (length prio) 3) (setq prio '("A" "C" "B")))
- (setq prio (mapcar 'string-to-char prio))
- (org-set-local 'org-highest-priority (nth 0 prio))
- (org-set-local 'org-lowest-priority (nth 1 prio))
- (org-set-local 'org-default-priority (nth 2 prio)))
- (and props (org-set-local 'org-file-properties (nreverse props)))
- (and drawers (org-set-local 'org-drawers drawers))
- (and arch (org-set-local 'org-archive-location arch))
- (and links (setq org-link-abbrev-alist-local (nreverse links)))
- ;; Process the TODO keywords
- (unless kwds
- ;; Use the global values as if they had been given locally.
- (setq kwds (default-value 'org-todo-keywords))
- (if (stringp (car kwds))
- (setq kwds (list (cons org-todo-interpretation
- (default-value 'org-todo-keywords)))))
- (setq kwds (reverse kwds)))
- (setq kwds (nreverse kwds))
- (let (inter kw)
- (dolist (kws kwds)
- (let ((kws (or
- (run-hook-with-args-until-success
- 'org-todo-setup-filter-hook kws)
- kws)))
- (setq inter (pop kws) sep (member "|" kws)
- kws0 (delete "|" (copy-sequence kws))
- kwsa nil
- kws1 (mapcar
- (lambda (x)
- ;; 1 2
- (if (string-match "^\\(.*?\\)\\(?:(\\([^!@/]\\)?.*?)\\)?$" x)
- (progn
- (setq kw (match-string 1 x)
- key (and (match-end 2) (match-string 2 x))
- log (org-extract-log-state-settings x))
- (push (cons kw (and key (string-to-char key))) kwsa)
- (and log (push log org-todo-log-states))
- kw)
- (error "Invalid TODO keyword %s" x)))
- kws0)
- kwsa (if kwsa (append '((:startgroup))
- (nreverse kwsa)
- '((:endgroup))))
- hw (car kws1)
- dws (if sep (org-remove-keyword-keys (cdr sep)) (last kws1))
- tail (list inter hw (car dws) (org-last dws))))
- (add-to-list 'org-todo-heads hw 'append)
- (push kws1 org-todo-sets)
- (setq org-done-keywords (append org-done-keywords dws nil))
- (setq org-todo-key-alist (append org-todo-key-alist kwsa))
- (mapc (lambda (x) (push (cons x tail) org-todo-kwd-alist)) kws1)
- (setq org-todo-keywords-1 (append org-todo-keywords-1 kws1 nil)))
+ (let ((alist (org--setup-collect-keywords
+ (org-make-options-regexp
+ (append '("FILETAGS" "TAGS" "SETUPFILE")
+ (and (not tags-only)
+ '("ARCHIVE" "CATEGORY" "COLUMNS" "CONSTANTS"
+ "LINK" "OPTIONS" "PRIORITIES" "PROPERTY"
+ "SEQ_TODO" "STARTUP" "TODO" "TYP_TODO")))))))
+ ;; Startup options. Get this early since it does change
+ ;; behavior for other options (e.g., tags).
+ (let ((startup (cdr (assq 'startup alist))))
+ (dolist (option startup)
+ (let ((entry (assoc-string option org-startup-options t)))
+ (when entry
+ (let ((var (nth 1 entry))
+ (val (nth 2 entry)))
+ (if (not (nth 3 entry)) (set (make-local-variable var) val)
+ (unless (listp (symbol-value var))
+ (set (make-local-variable var) nil))
+ (add-to-list var val)))))))
+ (setq-local org-file-tags
+ (mapcar #'org-add-prop-inherited
+ (cdr (assq 'filetags alist))))
+ (setq org-current-tag-alist
+ (append org-tag-persistent-alist
+ (let ((tags (cdr (assq 'tags alist))))
+ (if tags (org-tag-string-to-alist tags)
+ org-tag-alist))))
+ (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)))
+ ;; Archive location.
+ (let ((archive (cdr (assq 'archive alist))))
+ (when archive (setq-local org-archive-location archive)))
+ ;; Category.
+ (let ((cat (org-string-nw-p (cdr (assq 'category alist)))))
+ (when cat
+ (setq-local org-category (intern cat))
+ (setq-local org-file-properties
+ (org--update-property-plist
+ "CATEGORY" cat org-file-properties))))
+ ;; Columns.
+ (let ((column (cdr (assq 'columns alist))))
+ (when column (setq-local org-columns-default-format column)))
+ ;; Constants.
+ (setq org-table-formula-constants-local (cdr (assq 'constants alist)))
+ ;; Link abbreviations.
+ (let ((links (cdr (assq 'link alist))))
+ (when links (setq org-link-abbrev-alist-local (nreverse links))))
+ ;; Priorities.
+ (let ((priorities (cdr (assq 'priorities alist))))
+ (when priorities
+ (setq-local org-highest-priority (nth 0 priorities))
+ (setq-local org-lowest-priority (nth 1 priorities))
+ (setq-local org-default-priority (nth 2 priorities))))
+ ;; Scripts.
+ (let ((scripts (assq 'scripts alist)))
+ (when scripts
+ (setq-local org-use-sub-superscripts (cdr scripts))))
+ ;; TODO keywords.
+ (setq-local org-todo-kwd-alist nil)
+ (setq-local org-todo-key-alist nil)
+ (setq-local org-todo-key-trigger nil)
+ (setq-local org-todo-keywords-1 nil)
+ (setq-local org-done-keywords nil)
+ (setq-local org-todo-heads nil)
+ (setq-local org-todo-sets nil)
+ (setq-local org-todo-log-states nil)
+ (let ((todo-sequences
+ (or (nreverse (cdr (assq 'todo alist)))
+ (let ((d (default-value 'org-todo-keywords)))
+ (if (not (stringp (car d))) d
+ ;; XXX: Backward compatibility code.
+ (list (cons org-todo-interpretation d)))))))
+ (dolist (sequence todo-sequences)
+ (let* ((sequence (or (run-hook-with-args-until-success
+ 'org-todo-setup-filter-hook sequence)
+ sequence))
+ (sequence-type (car sequence))
+ (keywords (cdr sequence))
+ (sep (member "|" keywords))
+ names alist)
+ (dolist (k (remove "|" keywords))
+ (unless (string-match "^\\(.*?\\)\\(?:(\\([^!@/]\\)?.*?)\\)?$"
+ k)
+ (error "Invalid TODO keyword %s" k))
+ (let ((name (match-string 1 k))
+ (key (match-string 2 k))
+ (log (org-extract-log-state-settings k)))
+ (push name names)
+ (push (cons name (and key (string-to-char key))) alist)
+ (when log (push log org-todo-log-states))))
+ (let* ((names (nreverse names))
+ (done (if sep (org-remove-keyword-keys (cdr sep))
+ (last names)))
+ (head (car names))
+ (tail (list sequence-type head (car done) (org-last done))))
+ (add-to-list 'org-todo-heads head 'append)
+ (push names org-todo-sets)
+ (setq org-done-keywords (append org-done-keywords done nil))
+ (setq org-todo-keywords-1 (append org-todo-keywords-1 names nil))
+ (setq org-todo-key-alist
+ (append org-todo-key-alist
+ (and alist
+ (append '((:startgroup))
+ (nreverse alist)
+ '((:endgroup))))))
+ (dolist (k names) (push (cons k tail) org-todo-kwd-alist))))))
(setq org-todo-sets (nreverse org-todo-sets)
org-todo-kwd-alist (nreverse org-todo-kwd-alist)
- org-todo-key-trigger (delq nil (mapcar 'cdr org-todo-key-alist))
- org-todo-key-alist (org-assign-fast-keys org-todo-key-alist)))
- ;; Compute the regular expressions and other local variables.
- ;; Using `org-outline-regexp-bol' would complicate them much,
- ;; because of the fixed white space at the end of that string.
- (if (not org-done-keywords)
- (setq org-done-keywords (and org-todo-keywords-1
- (list (org-last org-todo-keywords-1)))))
- (setq org-ds-keyword-length (+ 2 (max (length org-deadline-string)
- (length org-scheduled-string)
- (length org-clock-string)
- (length org-closed-string)))
- org-drawer-regexp
- (concat "^[ \t]*:\\("
- (mapconcat 'regexp-quote org-drawers "\\|")
- "\\):[ \t]*$")
- org-not-done-keywords
- (org-delete-all org-done-keywords (copy-sequence org-todo-keywords-1))
- org-todo-regexp
- (concat "\\("
- (mapconcat 'regexp-quote org-todo-keywords-1 "\\|")
- "\\)")
- org-not-done-regexp
- (concat "\\("
- (mapconcat 'regexp-quote org-not-done-keywords "\\|")
- "\\)")
- org-not-done-heading-regexp
- (format org-heading-keyword-regexp-format org-not-done-regexp)
- org-todo-line-regexp
- (format org-heading-keyword-maybe-regexp-format org-todo-regexp)
- org-complex-heading-regexp
- (concat "^\\(\\*+\\)"
- "\\(?: +" org-todo-regexp "\\)?"
- "\\(?: +\\(\\[#.\\]\\)\\)?"
- "\\(?: +\\(.*?\\)\\)??"
- (org-re "\\(?:[ \t]+\\(:[[:alnum:]_@#%:]+:\\)\\)?")
- "[ \t]*$")
- org-complex-heading-regexp-format
- (concat "^\\(\\*+\\)"
- "\\(?: +" org-todo-regexp "\\)?"
- "\\(?: +\\(\\[#.\\]\\)\\)?"
- "\\(?: +"
- ;; Stats cookies can be stuck to body.
- "\\(?:\\[[0-9%%/]+\\] *\\)*"
- "\\(%s\\)"
- "\\(?: *\\[[0-9%%/]+\\]\\)*"
- "\\)"
- (org-re "\\(?:[ \t]+\\(:[[:alnum:]_@#%%:]+:\\)\\)?")
- "[ \t]*$")
- org-todo-line-tags-regexp
- (concat "^\\(\\*+\\)"
- "\\(?: +" org-todo-regexp "\\)?"
- "\\(?: +\\(.*?\\)\\)??"
- (org-re "\\(?:[ \t]+\\(:[[:alnum:]:_@#%]+:\\)\\)?")
- "[ \t]*$")
- org-deadline-regexp (concat "\\<" org-deadline-string)
- org-deadline-time-regexp
- (concat "\\<" org-deadline-string " *<\\([^>]+\\)>")
- org-deadline-time-hour-regexp
- (concat "\\<" org-deadline-string
- " *<\\([^>]+[0-9]\\{1,2\\}:[0-9]\\{2\\}[0-9-+:hdwmy \t.]*\\)>")
- org-deadline-line-regexp
- (concat "\\<\\(" org-deadline-string "\\).*")
- org-scheduled-regexp
- (concat "\\<" org-scheduled-string)
- org-scheduled-time-regexp
- (concat "\\<" org-scheduled-string " *<\\([^>]+\\)>")
- org-scheduled-time-hour-regexp
- (concat "\\<" org-scheduled-string
- " *<\\([^>]+[0-9]\\{1,2\\}:[0-9]\\{2\\}[0-9-+:hdwmy \t.]*\\)>")
- org-closed-time-regexp
- (concat "\\<" org-closed-string " *\\[\\([^]]+\\)\\]")
- org-keyword-time-regexp
- (concat "\\<\\(" org-scheduled-string
- "\\|" org-deadline-string
- "\\|" org-closed-string
- "\\|" org-clock-string "\\)"
- " *[[<]\\([^]>]+\\)[]>]")
- org-keyword-time-not-clock-regexp
- (concat "\\<\\(" org-scheduled-string
- "\\|" org-deadline-string
- "\\|" org-closed-string
- "\\)"
- " *[[<]\\([^]>]+\\)[]>]")
- org-maybe-keyword-time-regexp
- (concat "\\(\\<\\(" org-scheduled-string
- "\\|" org-deadline-string
- "\\|" org-closed-string
- "\\|" org-clock-string "\\)\\)?"
- " *\\([[<][0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} ?[^]\r\n>]*?[]>]\\|<%%([^\r\n>]*>\\)")
- org-all-time-keywords
- (mapcar (lambda (w) (substring w 0 -1))
- (list org-scheduled-string org-deadline-string
- org-clock-string org-closed-string)))
- (setq org-ota nil)
- (org-compute-latex-and-related-regexp))))
+ org-todo-key-trigger (delq nil (mapcar #'cdr org-todo-key-alist))
+ org-todo-key-alist (org-assign-fast-keys org-todo-key-alist))
+ ;; Compute the regular expressions and other local variables.
+ ;; Using `org-outline-regexp-bol' would complicate them much,
+ ;; because of the fixed white space at the end of that string.
+ (unless org-done-keywords
+ (setq org-done-keywords
+ (and org-todo-keywords-1 (last org-todo-keywords-1))))
+ (setq org-not-done-keywords
+ (org-delete-all org-done-keywords
+ (copy-sequence org-todo-keywords-1))
+ org-todo-regexp (regexp-opt org-todo-keywords-1 t)
+ org-not-done-regexp (regexp-opt org-not-done-keywords t)
+ org-not-done-heading-regexp
+ (format org-heading-keyword-regexp-format org-not-done-regexp)
+ org-todo-line-regexp
+ (format org-heading-keyword-maybe-regexp-format org-todo-regexp)
+ org-complex-heading-regexp
+ (concat "^\\(\\*+\\)"
+ "\\(?: +" org-todo-regexp "\\)?"
+ "\\(?: +\\(\\[#.\\]\\)\\)?"
+ "\\(?: +\\(.*?\\)\\)??"
+ "\\(?:[ \t]+\\(:[[:alnum:]_@#%:]+:\\)\\)?"
+ "[ \t]*$")
+ org-complex-heading-regexp-format
+ (concat "^\\(\\*+\\)"
+ "\\(?: +" org-todo-regexp "\\)?"
+ "\\(?: +\\(\\[#.\\]\\)\\)?"
+ "\\(?: +"
+ ;; Stats cookies can be stuck to body.
+ "\\(?:\\[[0-9%%/]+\\] *\\)*"
+ "\\(%s\\)"
+ "\\(?: *\\[[0-9%%/]+\\]\\)*"
+ "\\)"
+ "\\(?:[ \t]+\\(:[[:alnum:]_@#%%:]+:\\)\\)?"
+ "[ \t]*$")
+ org-todo-line-tags-regexp
+ (concat "^\\(\\*+\\)"
+ "\\(?: +" org-todo-regexp "\\)?"
+ "\\(?: +\\(.*?\\)\\)??"
+ "\\(?:[ \t]+\\(:[[:alnum:]:_@#%]+:\\)\\)?"
+ "[ \t]*$"))
+ (org-compute-latex-and-related-regexp)))))
+
+(defun org--setup-collect-keywords (regexp &optional files alist)
+ "Return setup keywords values as an alist.
+
+REGEXP matches a subset of setup keywords. FILES is a list of
+file names already visited. It is used to avoid circular setup
+files. ALIST, when non-nil, is the alist computed so far.
+
+Return value contains the following keys: `archive', `category',
+`columns', `constants', `filetags', `link', `priorities',
+`property', `scripts', `startup', `tags' and `todo'."
+ (org-with-wide-buffer
+ (goto-char (point-min))
+ (let ((case-fold-search t))
+ (while (re-search-forward regexp nil t)
+ (let ((element (org-element-at-point)))
+ (when (eq (org-element-type element) 'keyword)
+ (let ((key (org-element-property :key element))
+ (value (org-element-property :value element)))
+ (cond
+ ((equal key "ARCHIVE")
+ (when (org-string-nw-p value)
+ (push (cons 'archive value) alist)))
+ ((equal key "CATEGORY") (push (cons 'category value) alist))
+ ((equal key "COLUMNS") (push (cons 'columns value) alist))
+ ((equal key "CONSTANTS")
+ (let* ((constants (assq 'constants alist))
+ (store (cdr constants)))
+ (dolist (pair (org-split-string value))
+ (when (string-match "^\\([a-zA-Z0][_a-zA-Z0-9]*\\)=\\(.*\\)"
+ pair)
+ (let* ((name (match-string 1 pair))
+ (value (match-string 2 pair))
+ (old (assoc name store)))
+ (if old (setcdr old value)
+ (push (cons name value) store)))))
+ (if constants (setcdr constants store)
+ (push (cons 'constants store) alist))))
+ ((equal key "FILETAGS")
+ (when (org-string-nw-p value)
+ (let ((old (assq 'filetags alist))
+ (new (apply #'nconc
+ (mapcar (lambda (x) (org-split-string x ":"))
+ (org-split-string value)))))
+ (if old (setcdr old (append new (cdr old)))
+ (push (cons 'filetags new) alist)))))
+ ((equal key "LINK")
+ (when (string-match "\\`\\(\\S-+\\)[ \t]+\\(.+\\)" value)
+ (let ((links (assq 'link alist))
+ (pair (cons (match-string-no-properties 1 value)
+ (match-string-no-properties 2 value))))
+ (if links (push pair (cdr links))
+ (push (list 'link pair) alist)))))
+ ((equal key "OPTIONS")
+ (when (and (org-string-nw-p value)
+ (string-match "\\^:\\(t\\|nil\\|{}\\)" value))
+ (push (cons 'scripts (read (match-string 1 value))) alist)))
+ ((equal key "PRIORITIES")
+ (push (cons 'priorities
+ (let ((prio (org-split-string value)))
+ (if (< (length prio) 3) '(?A ?C ?B)
+ (mapcar #'string-to-char prio))))
+ alist))
+ ((equal key "PROPERTY")
+ (when (string-match "\\(\\S-+\\)[ \t]+\\(.*\\)" value)
+ (let* ((property (assq 'property alist))
+ (value (org--update-property-plist
+ (match-string-no-properties 1 value)
+ (match-string-no-properties 2 value)
+ (cdr property))))
+ (if property (setcdr property value)
+ (push (cons 'property value) alist)))))
+ ((equal key "STARTUP")
+ (let ((startup (assq 'startup alist)))
+ (if startup
+ (setcdr startup
+ (append (cdr startup) (org-split-string value)))
+ (push (cons 'startup (org-split-string value)) alist))))
+ ((equal key "TAGS")
+ (let ((tag-cell (assq 'tags alist)))
+ (if tag-cell
+ (setcdr tag-cell (concat (cdr tag-cell) "\n" value))
+ (push (cons 'tags value) alist))))
+ ((member key '("TODO" "SEQ_TODO" "TYP_TODO"))
+ (let ((todo (assq 'todo alist))
+ (value (cons (if (equal key "TYP_TODO") 'type 'sequence)
+ (org-split-string value))))
+ (if todo (push value (cdr todo))
+ (push (list 'todo value) alist))))
+ ((equal key "SETUPFILE")
+ (unless buffer-read-only ; Do not check in Gnus messages.
+ (let ((f (and (org-string-nw-p value)
+ (expand-file-name
+ (org-unbracket-string "\"" "\"" value)))))
+ (when (and f (file-readable-p f) (not (member f files)))
+ (with-temp-buffer
+ (setq default-directory (file-name-directory f))
+ (insert-file-contents f)
+ (setq alist
+ ;; Fake Org mode to benefit from cache
+ ;; without recurring needlessly.
+ (let ((major-mode 'org-mode))
+ (org--setup-collect-keywords
+ regexp (cons f files) alist)))))))))))))))
+ alist)
+
+(defun org-tag-string-to-alist (s)
+ "Return tag alist associated to string S.
+S is a value for TAGS keyword or produced with
+`org-tag-alist-to-string'. Return value is an alist suitable for
+`org-tag-alist' or `org-tag-persistent-alist'."
+ (let ((lines (mapcar #'split-string (split-string s "\n" t)))
+ (tag-re (concat "\\`\\([[:alnum:]_@#%]+"
+ "\\|{.+?}\\)" ; regular expression
+ "\\(?:(\\(.\\))\\)?\\'"))
+ alist group-flag)
+ (dolist (tokens lines (cdr (nreverse alist)))
+ (push '(:newline) alist)
+ (while tokens
+ (let ((token (pop tokens)))
+ (pcase token
+ ("{"
+ (push '(:startgroup) alist)
+ (when (equal (nth 1 tokens) ":") (setq group-flag t)))
+ ("}"
+ (push '(:endgroup) alist)
+ (setq group-flag nil))
+ ("["
+ (push '(:startgrouptag) alist)
+ (when (equal (nth 1 tokens) ":") (setq group-flag t)))
+ ("]"
+ (push '(:endgrouptag) alist)
+ (setq group-flag nil))
+ (":"
+ (push '(:grouptags) alist))
+ ((guard (string-match tag-re token))
+ (let ((tag (match-string 1 token))
+ (key (and (match-beginning 2)
+ (string-to-char (match-string 2 token)))))
+ ;; Push all tags in groups, no matter if they already
+ ;; appear somewhere else in the list.
+ (when (or group-flag (not (assoc tag alist)))
+ (push (cons tag key) alist))))))))))
+
+(defun org-tag-alist-to-string (alist &optional skip-key)
+ "Return tag string associated to ALIST.
+
+ALIST is an alist, as defined in `org-tag-alist' or
+`org-tag-persistent-alist', or produced with
+`org-tag-string-to-alist'.
+
+Return value is a string suitable as a value for \"TAGS\"
+keyword.
+
+When optional argument SKIP-KEY is non-nil, skip selection keys
+next to tags."
+ (mapconcat (lambda (token)
+ (pcase token
+ (`(:startgroup) "{")
+ (`(:endgroup) "}")
+ (`(:startgrouptag) "[")
+ (`(:endgrouptag) "]")
+ (`(:grouptags) ":")
+ (`(:newline) "\\n")
+ ((and
+ (guard (not skip-key))
+ `(,(and tag (pred stringp)) . ,(and key (pred characterp))))
+ (format "%s(%c)" tag key))
+ (`(,(and tag (pred stringp)) . ,_) tag)
+ (_ (user-error "Invalid tag token: %S" token))))
+ alist
+ " "))
+
+(defun org-tag-alist-to-groups (alist)
+ "Return group alist from tag ALIST.
+ALIST is an alist, as defined in `org-tag-alist' or
+`org-tag-persistent-alist', or produced with
+`org-tag-string-to-alist'. Return value is an alist following
+the pattern (GROUP-TAG TAGS) where GROUP-TAG is the tag, as
+a string, summarizing TAGS, as a list of strings."
+ (let (groups group-status current-group)
+ (dolist (token alist (nreverse groups))
+ (pcase token
+ (`(,(or :startgroup :startgrouptag)) (setq group-status t))
+ (`(,(or :endgroup :endgrouptag))
+ (when (eq group-status 'append)
+ (push (nreverse current-group) groups))
+ (setq group-status nil))
+ (`(:grouptags) (setq group-status 'append))
+ ((and `(,tag . ,_) (guard group-status))
+ (if (eq group-status 'append) (push tag current-group)
+ (setq current-group (list tag))))
+ (_ nil)))))
(defun org-file-contents (file &optional noerror)
"Return the contents of FILE, as a string."
- (if (or (not file) (not (file-readable-p file)))
- (if (not noerror)
- (error "Cannot read file \"%s\"" file)
- (message "Cannot read file \"%s\"" file)
- "")
- (with-temp-buffer
- (insert-file-contents file)
- (buffer-string))))
+ (if (and file (file-readable-p file))
+ (with-temp-buffer
+ (insert-file-contents file)
+ (buffer-string))
+ (funcall (if noerror 'message 'error)
+ "Cannot read file \"%s\"%s"
+ file
+ (let ((from (buffer-file-name (buffer-base-buffer))))
+ (if from (concat " (referenced in file \"" from "\")") "")))))
(defun org-extract-log-state-settings (x)
"Extract the log state setting from a TODO keyword string.
This will extract info from a string like \"WAIT(w@/!)\"."
- (let (kw key log1 log2)
- (when (string-match "^\\(.*?\\)\\(?:(\\([^!@/]\\)?\\([!@]\\)?\\(?:/\\([!@]\\)\\)?)\\)?$" x)
- (setq kw (match-string 1 x)
- key (and (match-end 2) (match-string 2 x))
- log1 (and (match-end 3) (match-string 3 x))
- log2 (and (match-end 4) (match-string 4 x)))
+ (when (string-match "^\\(.*?\\)\\(?:(\\([^!@/]\\)?\\([!@]\\)?\\(?:/\\([!@]\\)\\)?)\\)?$" x)
+ (let ((kw (match-string 1 x))
+ (log1 (and (match-end 3) (match-string 3 x)))
+ (log2 (and (match-end 4) (match-string 4 x))))
(and (or log1 log2)
(list kw
(and log1 (if (equal log1 "!") 'time 'note))
@@ -5216,8 +5469,8 @@ This will extract info from a string like \"WAIT(w@/!)\"."
(defun org-assign-fast-keys (alist)
"Assign fast keys to a keyword-key alist.
Respect keys that are already there."
- (let (new (alt ?0))
- (dolist (e alist)
+ (let (new e (alt ?0))
+ (while (setq e (pop alist))
(if (or (memq (car e) '(:newline :grouptags :endgroup :startgroup))
(cdr e)) ;; Key already assigned.
(push e new)
@@ -5229,7 +5482,7 @@ Respect keys that are already there."
(pop clist))
(unless clist
(while (rassoc alt used)
- (incf alt)))
+ (cl-incf alt)))
(push (cons (car e) (or (car clist) alt)) new))))
(nreverse new)))
@@ -5242,13 +5495,7 @@ Respect keys that are already there."
(defvar org-finish-function nil
"Function to be called when `C-c C-c' is used.
This is for getting out of special buffers like capture.")
-
-
-;; FIXME: Occasionally check by commenting these, to make sure
-;; no other functions uses these, forgetting to let-bind them.
-(org-no-warnings (defvar entry)) ;; unprefixed, from calendar.el
(defvar org-last-state)
-(org-no-warnings (defvar date)) ;; unprefixed, from calendar.el
;; Defined somewhere in this file, but used before definition.
(defvar org-entities) ;; defined in org-entities.el
@@ -5256,7 +5503,7 @@ This is for getting out of special buffers like capture.")
(defvar org-org-menu)
(defvar org-tbl-menu)
-;;;; Define the Org-mode
+;;;; Define the Org mode
;; We use a before-change function to check if a table might need
;; an update.
@@ -5264,7 +5511,7 @@ This is for getting out of special buffers like capture.")
"Indicates that a table might need an update.
This variable is set by `org-before-change-function'.
`org-table-align' sets it back to nil.")
-(defun org-before-change-function (beg end)
+(defun org-before-change-function (_beg _end)
"Every change indicates that a table might need an update."
(setq org-table-may-need-update t))
(defvar org-mode-map)
@@ -5278,13 +5525,12 @@ This variable is set by `org-before-change-function'.
(defvar buffer-face-mode-face)
(require 'outline)
-(if (and (not (keymapp outline-mode-map)) (featurep 'allout))
- (error "Conflict with outdated version of allout.el. Load org.el before allout.el, or upgrade to newer allout, for example by switching to Emacs 22"))
-(require 'noutline "noutline" 'noerror) ;; stock XEmacs does not have it
;; Other stuff we need.
(require 'time-date)
+(unless (fboundp 'time-subtract) (defalias 'time-subtract 'subtract-time))
(require 'easymenu)
+(autoload 'easy-menu-add "easymenu")
(require 'overlay)
;; (require 'org-macs) moved higher up in the file before it is first used
@@ -5305,15 +5551,15 @@ This variable is set by `org-before-change-function'.
"Outline-based notes management and organizer, alias
\"Carsten's outline-mode for keeping track of everything.\"
-Org-mode develops organizational tasks around a NOTES file which
-contains information about projects as plain text. Org-mode is
-implemented on top of outline-mode, which is ideal to keep the content
+Org mode develops organizational tasks around a NOTES file which
+contains information about projects as plain text. Org mode is
+implemented on top of Outline mode, which is ideal to keep the content
of large files well structured. It supports ToDo items, deadlines and
time stamps, which magically appear in the diary listing of the Emacs
calendar. Tables are easily created with a built-in table editor.
Plain text URL-like links connect to websites, emails (VM), Usenet
messages (Gnus), BBDB entries, and any files related to the project.
-For printing and sharing of notes, an Org-mode file (or a part of it)
+For printing and sharing of notes, an Org file (or a part of it)
can be exported as a structured ASCII or HTML file.
The following commands are available:
@@ -5323,29 +5569,18 @@ The following commands are available:
;; Get rid of Outline menus, they are not needed
;; Need to do this here because define-derived-mode sets up
;; the keymap so late. Still, it is a waste to call this each time
- ;; we switch another buffer into org-mode.
- (if (featurep 'xemacs)
- (when (boundp 'outline-mode-menu-heading)
- ;; Assume this is Greg's port, it uses easymenu
- (easy-menu-remove outline-mode-menu-heading)
- (easy-menu-remove outline-mode-menu-show)
- (easy-menu-remove outline-mode-menu-hide))
- (define-key org-mode-map [menu-bar headings] 'undefined)
- (define-key org-mode-map [menu-bar hide] 'undefined)
- (define-key org-mode-map [menu-bar show] 'undefined))
+ ;; we switch another buffer into Org mode.
+ (define-key org-mode-map [menu-bar headings] 'undefined)
+ (define-key org-mode-map [menu-bar hide] 'undefined)
+ (define-key org-mode-map [menu-bar show] 'undefined)
(org-load-modules-maybe)
- (when (featurep 'xemacs)
- (easy-menu-add org-org-menu)
- (easy-menu-add org-tbl-menu))
(org-install-agenda-files-menu)
- (if org-descriptive-links (add-to-invisibility-spec '(org-link)))
+ (when org-descriptive-links (add-to-invisibility-spec '(org-link)))
(add-to-invisibility-spec '(org-cwidth))
(add-to-invisibility-spec '(org-hide-block . t))
- (when (featurep 'xemacs)
- (org-set-local 'line-move-ignore-invisible t))
- (org-set-local 'outline-regexp org-outline-regexp)
- (org-set-local 'outline-level 'org-outline-level)
+ (setq-local outline-regexp org-outline-regexp)
+ (setq-local outline-level 'org-outline-level)
(setq bidi-paragraph-direction 'left-to-right)
(when (and org-ellipsis
(fboundp 'set-display-table-slot) (boundp 'buffer-display-table)
@@ -5354,55 +5589,50 @@ The following commands are available:
(setq org-display-table (make-display-table)))
(set-display-table-slot
org-display-table 4
- (vconcat (mapcar
- (lambda (c) (make-glyph-code c (and (not (stringp org-ellipsis))
- org-ellipsis)))
- (if (stringp org-ellipsis) org-ellipsis "..."))))
+ (vconcat (mapcar (lambda (c) (make-glyph-code c 'org-ellipsis))
+ (if (stringp org-ellipsis) org-ellipsis "..."))))
(setq buffer-display-table org-display-table))
- (org-set-regexps-and-options-for-tags)
(org-set-regexps-and-options)
(org-set-font-lock-defaults)
(when (and org-tag-faces (not org-tags-special-faces-re))
;; tag faces set outside customize.... force initialization.
(org-set-tag-faces 'org-tag-faces org-tag-faces))
;; Calc embedded
- (org-set-local 'calc-embedded-open-mode "# ")
+ (setq-local calc-embedded-open-mode "# ")
;; Modify a few syntax entries
(modify-syntax-entry ?@ "w")
(modify-syntax-entry ?\" "\"")
(modify-syntax-entry ?\\ "_")
(modify-syntax-entry ?~ "_")
- (if org-startup-truncated (setq truncate-lines t))
- (when org-startup-indented (require 'org-indent) (org-indent-mode 1))
- (org-set-local 'font-lock-unfontify-region-function
- 'org-unfontify-region)
+ (setq-local font-lock-unfontify-region-function 'org-unfontify-region)
;; Activate before-change-function
- (org-set-local 'org-table-may-need-update t)
- (org-add-hook 'before-change-functions 'org-before-change-function nil
- 'local)
+ (setq-local org-table-may-need-update t)
+ (add-hook 'before-change-functions 'org-before-change-function nil 'local)
;; Check for running clock before killing a buffer
- (org-add-hook 'kill-buffer-hook 'org-check-running-clock nil 'local)
+ (add-hook 'kill-buffer-hook 'org-check-running-clock nil 'local)
;; Initialize macros templates.
(org-macro-initialize-templates)
;; Initialize radio targets.
(org-update-radio-target-regexp)
;; Indentation.
- (org-set-local 'indent-line-function 'org-indent-line)
- (org-set-local 'indent-region-function 'org-indent-region)
+ (setq-local indent-line-function 'org-indent-line)
+ (setq-local indent-region-function 'org-indent-region)
;; Filling and auto-filling.
(org-setup-filling)
;; Comments.
(org-setup-comments-handling)
+ ;; Initialize cache.
+ (org-element-cache-reset)
;; Beginning/end of defun
- (org-set-local 'beginning-of-defun-function 'org-backward-element)
- (org-set-local 'end-of-defun-function
- (lambda ()
- (if (not (org-at-heading-p))
- (org-forward-element)
- (org-forward-element)
- (forward-char -1))))
+ (setq-local beginning-of-defun-function 'org-backward-element)
+ (setq-local end-of-defun-function
+ (lambda ()
+ (if (not (org-at-heading-p))
+ (org-forward-element)
+ (org-forward-element)
+ (forward-char -1))))
;; Next error for sparse trees
- (org-set-local 'next-error-function 'org-occur-next-match)
+ (setq-local next-error-function 'org-occur-next-match)
;; Make sure dependence stuff works reliably, even for users who set it
;; too late :-(
(if org-enforce-todo-dependencies
@@ -5417,78 +5647,65 @@ The following commands are available:
'org-block-todo-from-checkboxes))
;; Align options lines
- (org-set-local
- 'align-mode-rules-list
+ (setq-local
+ align-mode-rules-list
'((org-in-buffer-settings
- (regexp . "^#\\+[A-Z_]+:\\(\\s-*\\)\\S-+")
+ (regexp . "^[ \t]*#\\+[A-Z_]+:\\(\\s-*\\)\\S-+")
(modes . '(org-mode)))))
;; Imenu
- (org-set-local 'imenu-create-index-function
- 'org-imenu-get-tree)
+ (setq-local imenu-create-index-function 'org-imenu-get-tree)
;; Make isearch reveal context
- (if (or (featurep 'xemacs)
- (not (boundp 'outline-isearch-open-invisible-function)))
- ;; Emacs 21 and XEmacs make use of the hook
- (org-add-hook 'isearch-mode-end-hook 'org-isearch-end 'append 'local)
- ;; Emacs 22 deals with this through a special variable
- (org-set-local 'outline-isearch-open-invisible-function
- (lambda (&rest ignore) (org-show-context 'isearch))))
+ (setq-local outline-isearch-open-invisible-function
+ (lambda (&rest _) (org-show-context 'isearch)))
;; Setup the pcomplete hooks
- (set (make-local-variable 'pcomplete-command-completion-function)
- 'org-pcomplete-initial)
- (set (make-local-variable 'pcomplete-command-name-function)
- 'org-command-at-point)
- (set (make-local-variable 'pcomplete-default-completion-function)
- 'ignore)
- (set (make-local-variable 'pcomplete-parse-arguments-function)
- 'org-parse-arguments)
- (set (make-local-variable 'pcomplete-termination-string) "")
- (when (>= emacs-major-version 23)
- (set (make-local-variable 'buffer-face-mode-face) 'org-default))
-
- ;; If empty file that did not turn on org-mode automatically, make it to.
- (if (and org-insert-mode-line-in-empty-file
- (org-called-interactively-p 'any)
- (= (point-min) (point-max)))
- (insert "# -*- mode: org -*-\n\n"))
+ (setq-local pcomplete-command-completion-function 'org-pcomplete-initial)
+ (setq-local pcomplete-command-name-function 'org-command-at-point)
+ (setq-local pcomplete-default-completion-function 'ignore)
+ (setq-local pcomplete-parse-arguments-function 'org-parse-arguments)
+ (setq-local pcomplete-termination-string "")
+ (setq-local buffer-face-mode-face 'org-default)
+
+ ;; If empty file that did not turn on Org mode automatically, make
+ ;; it to.
+ (when (and org-insert-mode-line-in-empty-file
+ (called-interactively-p 'any)
+ (= (point-min) (point-max)))
+ (insert "# -*- mode: org -*-\n\n"))
(unless org-inhibit-startup
(org-unmodified
- (and org-startup-with-beamer-mode (org-beamer-mode))
+ (when org-startup-with-beamer-mode (org-beamer-mode))
(when org-startup-align-all-tables
- (org-table-map-tables 'org-table-align 'quietly))
- (when org-startup-with-inline-images
- (org-display-inline-images))
- (when org-startup-with-latex-preview
- (org-preview-latex-fragment))
- (unless org-inhibit-startup-visibility-stuff
- (org-set-startup-visibility))))
- ;; Try to set org-hide correctly
+ (org-table-map-tables #'org-table-align t))
+ (when org-startup-with-inline-images (org-display-inline-images))
+ (when org-startup-with-latex-preview (org-toggle-latex-fragment '(16)))
+ (unless org-inhibit-startup-visibility-stuff (org-set-startup-visibility))
+ (when org-startup-truncated (setq truncate-lines t))
+ (when org-startup-indented (require 'org-indent) (org-indent-mode 1))
+ (org-refresh-effort-properties)))
+ ;; Try to set `org-hide' face correctly.
(let ((foreground (org-find-invisible-foreground)))
- (if foreground
- (set-face-foreground 'org-hide foreground))))
+ (when foreground
+ (set-face-foreground 'org-hide foreground))))
;; Update `customize-package-emacs-version-alist'
(add-to-list 'customize-package-emacs-version-alist
'(Org ("6.21b" . "23.1") ("6.33x" . "23.2")
("7.8.11" . "24.1") ("7.9.4" . "24.3")
- ("8.2.6" . "24.4")))
+ ("8.2.6" . "24.4") ("8.2.10" . "24.5")
+ ("9.0" . "26.1")))
(defvar org-mode-transpose-word-syntax-table
- (let ((st (make-syntax-table)))
- (mapc (lambda(c) (modify-syntax-entry
- (string-to-char (car c)) "w p" st))
- org-emphasis-alist)
- st))
+ (let ((st (make-syntax-table text-mode-syntax-table)))
+ (dolist (c org-emphasis-alist st)
+ (modify-syntax-entry (string-to-char (car c)) "w p" st))))
(when (fboundp 'abbrev-table-put)
(abbrev-table-put org-mode-abbrev-table
:parents (list text-mode-abbrev-table)))
-(put 'org-mode 'flyspell-mode-predicate 'org-mode-flyspell-verify)
-
(defun org-find-invisible-foreground ()
(let ((candidates (remove
"unspecified-bg"
@@ -5498,7 +5715,7 @@ The following commands are available:
(mapcar
(lambda (alist)
(when (boundp alist)
- (cdr (assoc 'background-color (symbol-value alist)))))
+ (cdr (assq 'background-color (symbol-value alist)))))
'(default-frame-alist initial-frame-alist window-system-default-frame-alist))
(list (face-foreground 'org-hide))))))
(car (remove nil candidates))))
@@ -5541,8 +5758,6 @@ the rounding returns a past time."
(require 'font-lock)
(defconst org-non-link-chars "]\t\n\r<>")
-(defvar org-link-types '("http" "https" "ftp" "mailto" "file" "news"
- "shell" "elisp" "doi" "message"))
(defvar org-link-types-re nil
"Matches a link that has a url-like prefix like \"http:\"")
(defvar org-link-re-with-space nil
@@ -5591,27 +5806,26 @@ stacked delimiters is N. Escaping delimiters is not possible."
next (concat "\\(?:" nothing left next right "\\)+" nothing)))
(concat left "\\(" re "\\)" right)))
-(defvar org-match-substring-regexp
+(defconst org-match-substring-regexp
(concat
"\\(\\S-\\)\\([_^]\\)\\("
- "\\(" (org-create-multibrace-regexp "{" "}" org-match-sexp-depth) "\\)"
+ "\\(?:" (org-create-multibrace-regexp "{" "}" org-match-sexp-depth) "\\)"
"\\|"
- "\\(" (org-create-multibrace-regexp "(" ")" org-match-sexp-depth) "\\)"
+ "\\(?:" (org-create-multibrace-regexp "(" ")" org-match-sexp-depth) "\\)"
"\\|"
- "\\(\\(?:\\*\\|[-+]?[^-+*!@#$%^_ \t\r\n,:\"?<>~;./{}=()]+\\)\\)\\)")
+ "\\(?:\\*\\|[+-]?[[:alnum:].,\\]*[[:alnum:]]\\)\\)")
"The regular expression matching a sub- or superscript.")
-(defvar org-match-substring-with-braces-regexp
+(defconst org-match-substring-with-braces-regexp
(concat
- "\\(\\S-\\)\\([_^]\\)\\("
- "\\(" (org-create-multibrace-regexp "{" "}" org-match-sexp-depth) "\\)"
- "\\)")
+ "\\(\\S-\\)\\([_^]\\)"
+ "\\(" (org-create-multibrace-regexp "{" "}" org-match-sexp-depth) "\\)")
"The regular expression matching a sub- or superscript, forcing braces.")
(defun org-make-link-regexps ()
"Update the link regular expressions.
-This should be called after the variable `org-link-types' has changed."
- (let ((types-re (regexp-opt org-link-types t)))
+This should be called after the variable `org-link-parameters' has changed."
+ (let ((types-re (regexp-opt (org-link-types) t)))
(setq org-link-types-re
(concat "\\`" types-re ":")
org-link-re-with-space
@@ -5629,14 +5843,12 @@ This should be called after the variable `org-link-types' has changed."
"\\([^" org-non-link-chars " ]"
"[^\t\n\r]*\\)")
org-angle-link-re
- (concat "<" types-re ":"
- "\\([^" org-non-link-chars " ]"
- "[^" org-non-link-chars "]*"
- "\\)>")
+ (format "<%s:\\([^>\n]*\\(?:\n[ \t]*[^> \t\n][^>\n]*\\)*\\)>"
+ types-re)
org-plain-link-re
(concat
"\\<" types-re ":"
- (org-re "\\([^ \t\n()<>]+\\(?:([[:word:]0-9_]+)\\|\\([^[:punct:] \t\n]\\|/\\)\\)\\)"))
+ "\\([^][ \t\n()<>]+\\(?:([[:word:]0-9_]+)\\|\\([^[:punct:] \t\n]\\|/\\)\\)\\)")
;; "\\([^]\t\n\r<>() ]+[^]\t\n\r<>,.;() ]\\)")
org-bracket-link-regexp
"\\[\\[\\([^][]+\\)\\]\\(\\[\\([^][]+\\)\\]\\)?\\]"
@@ -5651,77 +5863,46 @@ This should be called after the variable `org-link-types' has changed."
org-bracket-link-analytic-regexp++
(concat
"\\[\\["
- "\\(" (regexp-opt (cons "coderef" org-link-types) t) ":\\)?"
+ "\\(" (regexp-opt (cons "coderef" (org-link-types)) t) ":\\)?"
"\\([^]]+\\)"
"\\]"
"\\(\\[" "\\([^]]+\\)" "\\]\\)?"
- "\\]")
- org-any-link-re
- (concat "\\(" org-bracket-link-regexp "\\)\\|\\("
- org-angle-link-re "\\)\\|\\("
- org-plain-link-re "\\)"))))
-
-(org-make-link-regexps)
-
-(defconst org-ts-regexp "<\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} ?[^\r\n>]*?\\)>"
- "Regular expression for fast time stamp matching.")
-(defconst org-ts-regexp-both "[[<]\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} ?[^]\r\n>]*?\\)[]>]"
- "Regular expression for fast time stamp matching.")
-(defconst org-ts-regexp0
- "\\(\\([0-9]\\{4\\}\\)-\\([0-9]\\{2\\}\\)-\\([0-9]\\{2\\}\\)\\( +[^]+0-9>\r\n -]+\\)?\\( +\\([0-9]\\{1,2\\}\\):\\([0-9]\\{2\\}\\)\\)?\\)"
- "Regular expression matching time strings for analysis.
-This one does not require the space after the date, so it can be used
-on a string that terminates immediately after the date.")
-(defconst org-ts-regexp1 "\\(\\([0-9]\\{4\\}\\)-\\([0-9]\\{2\\}\\)-\\([0-9]\\{2\\}\\) *\\([^]+0-9>\r\n -]*\\)\\( \\([0-9]\\{1,2\\}\\):\\([0-9]\\{2\\}\\)\\)?\\)"
- "Regular expression matching time strings for analysis.")
-(defconst org-ts-regexp2 (concat "<" org-ts-regexp1 "[^>\n]\\{0,16\\}>")
- "Regular expression matching time stamps, with groups.")
-(defconst org-ts-regexp3 (concat "[[<]" org-ts-regexp1 "[^]>\n]\\{0,16\\}[]>]")
- "Regular expression matching time stamps (also [..]), with groups.")
-(defconst org-tr-regexp (concat org-ts-regexp "--?-?" org-ts-regexp)
- "Regular expression matching a time stamp range.")
-(defconst org-tr-regexp-both
- (concat org-ts-regexp-both "--?-?" org-ts-regexp-both)
- "Regular expression matching a time stamp range.")
-(defconst org-tsr-regexp (concat org-ts-regexp "\\(--?-?"
- org-ts-regexp "\\)?")
- "Regular expression matching a time stamp or time stamp range.")
-(defconst org-tsr-regexp-both
- (concat org-ts-regexp-both "\\(--?-?"
- org-ts-regexp-both "\\)?")
- "Regular expression matching a time stamp or time stamp range.
-The time stamps may be either active or inactive.")
+ "\\]")
+ org-any-link-re
+ (concat "\\(" org-bracket-link-regexp "\\)\\|\\("
+ org-angle-link-re "\\)\\|\\("
+ org-plain-link-re "\\)"))))
+
+(org-make-link-regexps)
(defvar org-emph-face nil)
(defun org-do-emphasis-faces (limit)
- "Run through the buffer and add overlays to emphasized strings."
+ "Run through the buffer and emphasize strings."
(let (rtn a)
(while (and (not rtn) (re-search-forward org-emph-re limit t))
(let* ((border (char-after (match-beginning 3)))
(bre (regexp-quote (char-to-string border))))
- (if (and (not (= border (char-after (match-beginning 4))))
- (not (save-match-data
- (string-match (concat bre ".*" bre)
- (replace-regexp-in-string
- "\n" " "
- (substring (match-string 2) 1 -1))))))
- (progn
- (setq rtn t)
- (setq a (assoc (match-string 3) org-emphasis-alist))
- (font-lock-prepend-text-property (match-beginning 2) (match-end 2)
- 'face
- (nth 1 a))
- (and (nth 2 a)
- (org-remove-flyspell-overlays-in
- (match-beginning 0) (match-end 0)))
- (add-text-properties (match-beginning 2) (match-end 2)
- '(font-lock-multiline t org-emphasis t))
- (when org-hide-emphasis-markers
- (add-text-properties (match-end 4) (match-beginning 5)
- '(invisible org-link))
- (add-text-properties (match-beginning 3) (match-end 3)
- '(invisible org-link))))))
+ (when (and (not (= border (char-after (match-beginning 4))))
+ (not (string-match-p (concat bre ".*" bre)
+ (replace-regexp-in-string
+ "\n" " "
+ (substring (match-string 2) 1 -1)))))
+ (setq rtn t)
+ (setq a (assoc (match-string 3) org-emphasis-alist))
+ (font-lock-prepend-text-property (match-beginning 2) (match-end 2)
+ 'face
+ (nth 1 a))
+ (and (nth 2 a)
+ (org-remove-flyspell-overlays-in
+ (match-beginning 0) (match-end 0)))
+ (add-text-properties (match-beginning 2) (match-end 2)
+ '(font-lock-multiline t org-emphasis t))
+ (when org-hide-emphasis-markers
+ (add-text-properties (match-end 4) (match-beginning 5)
+ '(invisible org-link))
+ (add-text-properties (match-beginning 3) (match-end 3)
+ '(invisible org-link)))))
(goto-char (1+ (match-beginning 0))))
rtn))
@@ -5736,19 +5917,20 @@ If CHAR is not given (for example in an interactive call) it will be
prompted for."
(interactive)
(let ((erc org-emphasis-regexp-components)
- (prompt "")
- (string "") beg end move c s)
+ (string "") beg end move s)
(if (org-region-active-p)
- (setq beg (region-beginning) end (region-end)
+ (setq beg (region-beginning)
+ end (region-end)
string (buffer-substring beg end))
(setq move t))
(unless char
(message "Emphasis marker or tag: [%s]"
- (mapconcat (lambda(e) (car e)) org-emphasis-alist ""))
+ (mapconcat #'car org-emphasis-alist ""))
(setq char (read-char-exclusive)))
- (if (equal char ?\ )
- (setq s "" move nil)
+ (if (equal char ?\s)
+ (setq s ""
+ move nil)
(unless (assoc (char-to-string char) org-emphasis-alist)
(user-error "No such emphasis marker: \"%c\"" char))
(setq s (char-to-string char)))
@@ -5757,7 +5939,7 @@ prompted for."
(assoc (substring string 0 1) org-emphasis-alist))
(setq string (substring string 1 -1)))
(setq string (concat s string s))
- (if beg (delete-region beg end))
+ (when beg (delete-region beg end))
(unless (or (bolp)
(string-match (concat "[" (nth 0 erc) "\n]")
(char-to-string (char-before (point)))))
@@ -5775,37 +5957,86 @@ prompted for."
(defsubst org-rear-nonsticky-at (pos)
(add-text-properties (1- pos) pos (list 'rear-nonsticky org-nonsticky-props)))
-(defun org-activate-plain-links (limit)
- "Run through the buffer and add overlays to links."
- (let (f hl)
- (when (and (re-search-forward (concat org-plain-link-re) limit t)
- (not (org-in-src-block-p)))
- (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0))
- (setq f (get-text-property (match-beginning 0) 'face))
- (setq hl (org-match-string-no-properties 0))
- (if (or (eq f 'org-tag)
- (and (listp f) (memq 'org-tag f)))
- nil
- (add-text-properties (match-beginning 0) (match-end 0)
- (list 'mouse-face 'highlight
- 'face 'org-link
- 'htmlize-link `(:uri ,hl)
- 'keymap org-mouse-map))
- (org-rear-nonsticky-at (match-end 0)))
- t)))
+(defun org-activate-links (limit)
+ "Add link properties to links.
+This includes angle, plain, and bracket links."
+ (catch :exit
+ (while (re-search-forward org-any-link-re limit t)
+ (let* ((start (match-beginning 0))
+ (end (match-end 0))
+ (style (cond ((eq ?< (char-after start)) 'angle)
+ ((eq ?\[ (char-after (1+ start))) 'bracket)
+ (t 'plain))))
+ (when (and (memq style org-highlight-links)
+ ;; Do not confuse plain links with tags.
+ (not (and (eq style 'plain)
+ (let ((face (get-text-property
+ (max (1- start) (point-min)) 'face)))
+ (if (consp face) (memq 'org-tag face)
+ (eq 'org-tag face))))))
+ (let* ((link-object (save-excursion
+ (goto-char start)
+ (save-match-data (org-element-link-parser))))
+ (link (org-element-property :raw-link link-object))
+ (type (org-element-property :type link-object))
+ (path (org-element-property :path link-object))
+ (properties ;for link's visible part
+ (list
+ 'face (pcase (org-link-get-parameter type :face)
+ ((and (pred functionp) face) (funcall face path))
+ ((and (pred facep) face) face)
+ ((and (pred consp) face) face) ;anonymous
+ (_ 'org-link))
+ 'mouse-face (or (org-link-get-parameter type :mouse-face)
+ 'highlight)
+ 'keymap (or (org-link-get-parameter type :keymap)
+ org-mouse-map)
+ 'help-echo (pcase (org-link-get-parameter type :help-echo)
+ ((and (pred stringp) echo) echo)
+ ((and (pred functionp) echo) echo)
+ (_ (concat "LINK: " link)))
+ 'htmlize-link (pcase (org-link-get-parameter type
+ :htmlize-link)
+ ((and (pred functionp) f) (funcall f))
+ (_ `(:uri ,link)))
+ 'font-lock-multiline t)))
+ (org-remove-flyspell-overlays-in start end)
+ (org-rear-nonsticky-at end)
+ (if (not (eq 'bracket style))
+ (add-text-properties start end properties)
+ ;; Handle invisible parts in bracket links.
+ (remove-text-properties start end '(invisible nil))
+ (let ((hidden
+ (append `(invisible
+ ,(or (org-link-get-parameter type :display)
+ 'org-link))
+ properties))
+ (visible-start (or (match-beginning 4) (match-beginning 2)))
+ (visible-end (or (match-end 4) (match-end 2))))
+ (add-text-properties start visible-start hidden)
+ (add-text-properties visible-start visible-end properties)
+ (add-text-properties visible-end end hidden)
+ (org-rear-nonsticky-at visible-start)
+ (org-rear-nonsticky-at visible-end)))
+ (let ((f (org-link-get-parameter type :activate-func)))
+ (when (functionp f)
+ (funcall f start end path (eq style 'bracket))))
+ (throw :exit t))))) ;signal success
+ nil))
(defun org-activate-code (limit)
- (if (re-search-forward "^[ \t]*\\(:\\(?: .*\\|$\\)\n?\\)" limit t)
- (progn
- (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0))
- (remove-text-properties (match-beginning 0) (match-end 0)
- '(display t invisible t intangible t))
- t)))
+ (when (re-search-forward "^[ \t]*\\(:\\(?: .*\\|$\\)\n?\\)" limit t)
+ (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0))
+ (remove-text-properties (match-beginning 0) (match-end 0)
+ '(display t invisible t intangible t))
+ t))
-(defcustom org-src-fontify-natively nil
- "When non-nil, fontify code in code blocks."
+(defcustom org-src-fontify-natively t
+ "When non-nil, fontify code in code blocks.
+See also the `org-block' face."
:type 'boolean
- :version "24.1"
+ :version "24.4"
+ :package-version '(Org . "8.3")
:group 'org-appearance
:group 'org-babel)
@@ -5820,221 +6051,248 @@ by a #."
(defun org-fontify-meta-lines-and-blocks (limit)
(condition-case nil
(org-fontify-meta-lines-and-blocks-1 limit)
- (error (message "org-mode fontification error"))))
+ (error (message "org-mode fontification error in %S at %d"
+ (current-buffer)
+ (line-number-at-pos)))))
(defun org-fontify-meta-lines-and-blocks-1 (limit)
"Fontify #+ lines and blocks."
(let ((case-fold-search t))
- (if (re-search-forward
- "^\\([ \t]*#\\(\\(\\+[a-zA-Z]+:?\\| \\|$\\)\\(_\\([a-zA-Z]+\\)\\)?\\)[ \t]*\\(\\([^ \t\n]*\\)[ \t]*\\(.*\\)\\)\\)"
- limit t)
- (let ((beg (match-beginning 0))
- (block-start (match-end 0))
- (block-end nil)
- (lang (match-string 7))
- (beg1 (line-beginning-position 2))
- (dc1 (downcase (match-string 2)))
- (dc3 (downcase (match-string 3)))
- end end1 quoting block-type ovl)
- (cond
- ((member dc1 '("+html:" "+ascii:" "+latex:"))
- ;; a single line of backend-specific content
- (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0))
- (remove-text-properties (match-beginning 0) (match-end 0)
- '(display t invisible t intangible t))
- (add-text-properties (match-beginning 1) (match-end 3)
- '(font-lock-fontified t face org-meta-line))
- (add-text-properties (match-beginning 6) (+ (match-end 6) 1)
- '(font-lock-fontified t face org-block))
- ; for backend-specific code
- t)
- ((and (match-end 4) (equal dc3 "+begin"))
- ;; Truly a block
- (setq block-type (downcase (match-string 5))
- quoting (member block-type org-protecting-blocks))
- (when (re-search-forward
- (concat "^[ \t]*#\\+end" (match-string 4) "\\>.*")
- nil t) ;; on purpose, we look further than LIMIT
- (setq end (min (point-max) (match-end 0))
- end1 (min (point-max) (1- (match-beginning 0))))
- (setq block-end (match-beginning 0))
- (when quoting
- (remove-text-properties beg end
- '(display t invisible t intangible t)))
- (add-text-properties
- beg end
- '(font-lock-fontified t font-lock-multiline t))
- (add-text-properties beg beg1 '(face org-meta-line))
- (add-text-properties end1 (min (point-max) (1+ end))
- '(face org-meta-line)) ; for end_src
- (cond
- ((and lang (not (string= lang "")) org-src-fontify-natively)
- (org-src-font-lock-fontify-block lang block-start block-end)
- ;; remove old background overlays
- (mapc (lambda (ov)
- (if (eq (overlay-get ov 'face) 'org-block-background)
- (delete-overlay ov)))
- (overlays-at (/ (+ beg1 block-end) 2)))
- ;; add a background overlay
- (setq ovl (make-overlay beg1 block-end))
- (overlay-put ovl 'face 'org-block-background)
- (overlay-put ovl 'evaporate t)) ;; make it go away when empty
- (quoting
- (add-text-properties beg1 (min (point-max) (1+ end1))
- '(face org-block))) ; end of source block
- ((not org-fontify-quote-and-verse-blocks))
- ((string= block-type "quote")
- (add-text-properties beg1 (min (point-max) (1+ end1)) '(face org-quote)))
- ((string= block-type "verse")
- (add-text-properties beg1 (min (point-max) (1+ end1)) '(face org-verse))))
- (add-text-properties beg beg1 '(face org-block-begin-line))
- (add-text-properties (min (point-max) (1+ end)) (min (point-max) (1+ end1))
- '(face org-block-end-line))
- t))
- ((member dc1 '("+title:" "+author:" "+email:" "+date:"))
- (add-text-properties
- beg (match-end 3)
- (if (member (intern (substring dc1 1 -1)) org-hidden-keywords)
- '(font-lock-fontified t invisible t)
- '(font-lock-fontified t face org-document-info-keyword)))
- (add-text-properties
- (match-beginning 6) (min (point-max) (1+ (match-end 6)))
- (if (string-equal dc1 "+title:")
- '(font-lock-fontified t face org-document-title)
- '(font-lock-fontified t face org-document-info))))
- ((or (equal dc1 "+results")
- (member dc1 '("+begin:" "+end:" "+caption:" "+label:"
- "+orgtbl:" "+tblfm:" "+tblname:" "+results:"
- "+call:" "+header:" "+headers:" "+name:"))
- (and (match-end 4) (equal dc3 "+attr")))
- (add-text-properties
- beg (match-end 0)
- '(font-lock-fontified t face org-meta-line))
- t)
- ((member dc3 '(" " ""))
- (add-text-properties
- beg (match-end 0)
- '(font-lock-fontified t face font-lock-comment-face)))
- ((not (member (char-after beg) '(?\ ?\t)))
- ;; just any other in-buffer setting, but not indented
+ (when (re-search-forward
+ "^\\([ \t]*#\\(\\(\\+[a-zA-Z]+:?\\| \\|$\\)\\(_\\([a-zA-Z]+\\)\\)?\\)[ \t]*\\(\\([^ \t\n]*\\)[ \t]*\\(.*\\)\\)\\)"
+ limit t)
+ (let ((beg (match-beginning 0))
+ (block-start (match-end 0))
+ (block-end nil)
+ (lang (match-string 7))
+ (beg1 (line-beginning-position 2))
+ (dc1 (downcase (match-string 2)))
+ (dc3 (downcase (match-string 3)))
+ end end1 quoting block-type)
+ (cond
+ ((and (match-end 4) (equal dc3 "+begin"))
+ ;; Truly a block
+ (setq block-type (downcase (match-string 5))
+ quoting (member block-type org-protecting-blocks))
+ (when (re-search-forward
+ (concat "^[ \t]*#\\+end" (match-string 4) "\\>.*")
+ nil t) ;; on purpose, we look further than LIMIT
+ (setq end (min (point-max) (match-end 0))
+ end1 (min (point-max) (1- (match-beginning 0))))
+ (setq block-end (match-beginning 0))
+ (when quoting
+ (org-remove-flyspell-overlays-in beg1 end1)
+ (remove-text-properties beg end
+ '(display t invisible t intangible t)))
(add-text-properties
- beg (match-end 0)
- '(font-lock-fontified t face org-meta-line))
- t)
- (t nil))))))
-
-(defun org-activate-angle-links (limit)
- "Run through the buffer and add overlays to links."
- (if (and (re-search-forward org-angle-link-re limit t)
- (not (org-in-src-block-p)))
- (progn
- (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0))
- (add-text-properties (match-beginning 0) (match-end 0)
- (list 'mouse-face 'highlight
- 'keymap org-mouse-map))
- (org-rear-nonsticky-at (match-end 0))
- t)))
+ beg end '(font-lock-fontified t font-lock-multiline t))
+ (add-text-properties beg beg1 '(face org-meta-line))
+ (org-remove-flyspell-overlays-in beg beg1)
+ (add-text-properties ; For end_src
+ end1 (min (point-max) (1+ end)) '(face org-meta-line))
+ (org-remove-flyspell-overlays-in end1 end)
+ (cond
+ ((and lang (not (string= lang "")) org-src-fontify-natively)
+ (org-src-font-lock-fontify-block lang block-start block-end)
+ (add-text-properties beg1 block-end '(src-block t)))
+ (quoting
+ (add-text-properties beg1 (min (point-max) (1+ end1))
+ (list 'face
+ (list :inherit
+ (let ((face-name
+ (intern (format "org-block-%s" lang))))
+ (append (and (facep face-name) (list face-name))
+ '(org-block))))))) ; end of source block
+ ((not org-fontify-quote-and-verse-blocks))
+ ((string= block-type "quote")
+ (add-face-text-property
+ beg1 (min (point-max) (1+ end1)) 'org-quote t))
+ ((string= block-type "verse")
+ (add-face-text-property
+ beg1 (min (point-max) (1+ end1)) 'org-verse t)))
+ (add-text-properties beg beg1 '(face org-block-begin-line))
+ (add-text-properties (min (point-max) (1+ end)) (min (point-max) (1+ end1))
+ '(face org-block-end-line))
+ t))
+ ((member dc1 '("+title:" "+author:" "+email:" "+date:"))
+ (org-remove-flyspell-overlays-in
+ (match-beginning 0)
+ (if (equal "+title:" dc1) (match-end 2) (match-end 0)))
+ (add-text-properties
+ beg (match-end 3)
+ (if (member (intern (substring dc1 1 -1)) org-hidden-keywords)
+ '(font-lock-fontified t invisible t)
+ '(font-lock-fontified t face org-document-info-keyword)))
+ (add-text-properties
+ (match-beginning 6) (min (point-max) (1+ (match-end 6)))
+ (if (string-equal dc1 "+title:")
+ '(font-lock-fontified t face org-document-title)
+ '(font-lock-fontified t face org-document-info))))
+ ((string-prefix-p "+caption" dc1)
+ (org-remove-flyspell-overlays-in (match-end 2) (match-end 0))
+ (remove-text-properties (match-beginning 0) (match-end 0)
+ '(display t invisible t intangible t))
+ ;; Handle short captions.
+ (save-excursion
+ (beginning-of-line)
+ (looking-at "\\([ \t]*#\\+caption\\(?:\\[.*\\]\\)?:\\)[ \t]*"))
+ (add-text-properties (line-beginning-position) (match-end 1)
+ '(font-lock-fontified t face org-meta-line))
+ (add-text-properties (match-end 0) (line-end-position)
+ '(font-lock-fontified t face org-block))
+ t)
+ ((member dc3 '(" " ""))
+ (org-remove-flyspell-overlays-in beg (match-end 0))
+ (add-text-properties
+ beg (match-end 0)
+ '(font-lock-fontified t face font-lock-comment-face)))
+ (t ;; just any other in-buffer setting, but not indented
+ (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0))
+ (remove-text-properties (match-beginning 0) (match-end 0)
+ '(display t invisible t intangible t))
+ (add-text-properties beg (match-end 0)
+ '(font-lock-fontified t face org-meta-line))
+ t))))))
+
+(defun org-fontify-drawers (limit)
+ "Fontify drawers."
+ (when (re-search-forward org-drawer-regexp limit t)
+ (add-text-properties
+ (match-beginning 0) (match-end 0)
+ '(font-lock-fontified t face org-special-keyword))
+ (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0))
+ t))
+
+(defun org-fontify-macros (limit)
+ "Fontify macros."
+ (when (re-search-forward "\\({{{\\).+?\\(}}}\\)" limit t)
+ (add-text-properties
+ (match-beginning 0) (match-end 0)
+ '(font-lock-fontified t face org-macro))
+ (when org-hide-macro-markers
+ (add-text-properties (match-end 2) (match-beginning 2)
+ '(invisible t))
+ (add-text-properties (match-beginning 1) (match-end 1)
+ '(invisible t)))
+ (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0))
+ t))
(defun org-activate-footnote-links (limit)
- "Run through the buffer and add overlays to footnotes."
+ "Add text properties for footnotes."
(let ((fn (org-footnote-next-reference-or-definition limit)))
(when fn
- (let ((beg (nth 1 fn)) (end (nth 2 fn)))
- (org-remove-flyspell-overlays-in beg end)
+ (let* ((beg (nth 1 fn))
+ (end (nth 2 fn))
+ (label (car fn))
+ (referencep (/= (line-beginning-position) beg)))
+ (when (and referencep (nth 3 fn))
+ (save-excursion
+ (goto-char beg)
+ (search-forward (or label "fn:"))
+ (org-remove-flyspell-overlays-in beg (match-end 0))))
(add-text-properties beg end
(list 'mouse-face 'highlight
'keymap org-mouse-map
'help-echo
- (if (= (point-at-bol) beg)
- "Footnote definition"
- "Footnote reference")
+ (if referencep "Footnote reference"
+ "Footnote definition")
'font-lock-fontified t
'font-lock-multiline t
'face 'org-footnote))))))
-(defun org-activate-bracket-links (limit)
- "Run through the buffer and add overlays to bracketed links."
- (if (and (re-search-forward org-bracket-link-regexp limit t)
- (not (org-in-src-block-p)))
- (let* ((hl (org-match-string-no-properties 1))
- (help (concat "LINK: " (save-match-data (org-link-unescape hl))))
- (ip (org-maybe-intangible
- (list 'invisible 'org-link
- 'keymap org-mouse-map 'mouse-face 'highlight
- 'font-lock-multiline t 'help-echo help
- 'htmlize-link `(:uri ,hl))))
- (vp (list 'keymap org-mouse-map 'mouse-face 'highlight
- 'font-lock-multiline t 'help-echo help
- 'htmlize-link `(:uri ,hl))))
- ;; We need to remove the invisible property here. Table narrowing
- ;; may have made some of this invisible.
- (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0))
- (remove-text-properties (match-beginning 0) (match-end 0)
- '(invisible nil))
- (if (match-end 3)
- (progn
- (add-text-properties (match-beginning 0) (match-beginning 3) ip)
- (org-rear-nonsticky-at (match-beginning 3))
- (add-text-properties (match-beginning 3) (match-end 3) vp)
- (org-rear-nonsticky-at (match-end 3))
- (add-text-properties (match-end 3) (match-end 0) ip)
- (org-rear-nonsticky-at (match-end 0)))
- (add-text-properties (match-beginning 0) (match-beginning 1) ip)
- (org-rear-nonsticky-at (match-beginning 1))
- (add-text-properties (match-beginning 1) (match-end 1) vp)
- (org-rear-nonsticky-at (match-end 1))
- (add-text-properties (match-end 1) (match-end 0) ip)
- (org-rear-nonsticky-at (match-end 0)))
- t)))
-
(defun org-activate-dates (limit)
- "Run through the buffer and add overlays to dates."
- (if (and (re-search-forward org-tsr-regexp-both limit t)
- (not (equal (char-before (match-beginning 0)) 91)))
- (progn
- (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0))
- (add-text-properties (match-beginning 0) (match-end 0)
- (list 'mouse-face 'highlight
- 'keymap org-mouse-map))
- (org-rear-nonsticky-at (match-end 0))
- (when org-display-custom-times
- (if (match-end 3)
- (org-display-custom-time (match-beginning 3) (match-end 3)))
- (org-display-custom-time (match-beginning 1) (match-end 1)))
- t)))
-
-(defvar org-target-link-regexp nil
+ "Add text properties for dates."
+ (when (and (re-search-forward org-tsr-regexp-both limit t)
+ (not (equal (char-before (match-beginning 0)) 91)))
+ (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0))
+ (add-text-properties (match-beginning 0) (match-end 0)
+ (list 'mouse-face 'highlight
+ 'keymap org-mouse-map))
+ (org-rear-nonsticky-at (match-end 0))
+ (when org-display-custom-times
+ (if (match-end 3)
+ (org-display-custom-time (match-beginning 3) (match-end 3))
+ (org-display-custom-time (match-beginning 1) (match-end 1))))
+ t))
+
+(defvar-local org-target-link-regexp nil
"Regular expression matching radio targets in plain text.")
-(make-variable-buffer-local 'org-target-link-regexp)
-(defvar org-target-regexp "<<\\([^<>\n\r]+\\)>>"
+
+(defconst org-target-regexp (let ((border "[^<>\n\r \t]"))
+ (format "<<\\(%s\\|%s[^<>\n\r]*%s\\)>>"
+ border border border))
"Regular expression matching a link target.")
-(defvar org-radio-target-regexp "<<<\\([^<>\n\r]+\\)>>>"
+
+(defconst org-radio-target-regexp (format "<%s>" org-target-regexp)
"Regular expression matching a radio target.")
-(defvar org-any-target-regexp "<<\\([^<>\n\r]+\\)>>>?" ; FIXME, not exact, would match <<> as a radio target.
+
+(defconst org-any-target-regexp
+ (format "%s\\|%s" org-radio-target-regexp org-target-regexp)
"Regular expression matching any target.")
(defun org-activate-target-links (limit)
- "Run through the buffer and add overlays to target matches."
+ "Add text properties for target matches."
(when org-target-link-regexp
(let ((case-fold-search t))
- (if (re-search-forward org-target-link-regexp limit t)
- (progn
- (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0))
- (add-text-properties (match-beginning 0) (match-end 0)
- (list 'mouse-face 'highlight
- 'keymap org-mouse-map
- 'help-echo "Radio target link"
- 'org-linked-text t))
- (org-rear-nonsticky-at (match-end 0))
- t)))))
+ (when (re-search-forward org-target-link-regexp limit t)
+ (org-remove-flyspell-overlays-in (match-beginning 1) (match-end 1))
+ (add-text-properties (match-beginning 1) (match-end 1)
+ (list 'mouse-face 'highlight
+ 'keymap org-mouse-map
+ 'help-echo "Radio target link"
+ 'org-linked-text t))
+ (org-rear-nonsticky-at (match-end 1))
+ t))))
(defun org-update-radio-target-regexp ()
- "Find all radio targets in this file and update the regular expression."
+ "Find all radio targets in this file and update the regular expression.
+Also refresh fontification if needed."
(interactive)
- (when (memq 'radio org-activate-links)
+ (let ((old-regexp org-target-link-regexp)
+ (before-re "\\(?:^\\|[^[:alnum:]]\\)\\(")
+ (after-re "\\)\\(?:$\\|[^[:alnum:]]\\)")
+ (targets
+ (org-with-wide-buffer
+ (goto-char (point-min))
+ (let (rtn)
+ (while (re-search-forward org-radio-target-regexp nil t)
+ ;; Make sure point is really within the object.
+ (backward-char)
+ (let ((obj (org-element-context)))
+ (when (eq (org-element-type obj) 'radio-target)
+ (cl-pushnew (org-element-property :value obj) rtn
+ :test #'equal))))
+ rtn))))
(setq org-target-link-regexp
- (org-make-target-link-regexp (org-all-targets 'radio)))
- (org-restart-font-lock)))
+ (and targets
+ (concat before-re
+ (mapconcat
+ (lambda (x)
+ (replace-regexp-in-string
+ " +" "\\s-+" (regexp-quote x) t t))
+ targets
+ "\\|")
+ after-re)))
+ (unless (equal old-regexp org-target-link-regexp)
+ ;; Clean-up cache.
+ (let ((regexp (cond ((not old-regexp) org-target-link-regexp)
+ ((not org-target-link-regexp) old-regexp)
+ (t
+ (concat before-re
+ (mapconcat
+ (lambda (re)
+ (substring re (length before-re)
+ (- (length after-re))))
+ (list old-regexp org-target-link-regexp)
+ "\\|")
+ after-re)))))
+ (org-with-wide-buffer
+ (goto-char (point-min))
+ (while (re-search-forward regexp nil t)
+ (org-element-cache-refresh (match-beginning 1)))))
+ ;; Re fontify buffer.
+ (when (memq 'radio org-highlight-links)
+ (org-restart-font-lock)))))
(defun org-hide-wide-columns (limit)
(let (s e)
@@ -6042,20 +6300,18 @@ by a #."
'org-cwidth t))
(when s
(setq e (next-single-property-change s 'org-cwidth))
- (add-text-properties s e (org-maybe-intangible '(invisible org-cwidth)))
+ (add-text-properties s e '(invisible org-cwidth))
(goto-char e)
t)))
(defvar org-latex-and-related-regexp nil
"Regular expression for highlighting LaTeX, entities and sub/superscript.")
-(defvar org-match-substring-regexp)
-(defvar org-match-substring-with-braces-regexp)
(defun org-compute-latex-and-related-regexp ()
"Compute regular expression for LaTeX, entities and sub/superscript.
Result depends on variable `org-highlight-latex-and-related'."
- (org-set-local
- 'org-latex-and-related-regexp
+ (setq-local
+ org-latex-and-related-regexp
(let* ((re-sub
(cond ((not (memq 'script org-highlight-latex-and-related)) nil)
((eq org-use-sub-superscripts '{})
@@ -6081,9 +6337,13 @@ done, nil otherwise."
(when (org-string-nw-p org-latex-and-related-regexp)
(catch 'found
(while (re-search-forward org-latex-and-related-regexp limit t)
- (unless (memq (car-safe (get-text-property (1+ (match-beginning 0))
- 'face))
- '(org-code org-verbatim underline))
+ (unless
+ (cl-some
+ (lambda (f)
+ (memq f '(org-code org-verbatim underline org-special-keyword)))
+ (save-excursion
+ (goto-char (1+ (match-beginning 0)))
+ (face-at-point nil t)))
(let ((offset (if (memq (char-after (1+ (match-beginning 0)))
'(?_ ?^))
1
@@ -6102,63 +6362,32 @@ done, nil otherwise."
(font-lock-mode -1)
(font-lock-mode 1)))
-(defun org-all-targets (&optional radio)
- "Return a list of all targets in this file.
-When optional argument RADIO is non-nil, only find radio
-targets."
- (let ((re (if radio org-radio-target-regexp org-target-regexp)) rtn)
- (save-excursion
- (goto-char (point-min))
- (while (re-search-forward re nil t)
- ;; Make sure point is really within the object.
- (backward-char)
- (let ((obj (org-element-context)))
- (when (memq (org-element-type obj) '(radio-target target))
- (add-to-list 'rtn (downcase (org-element-property :value obj))))))
- rtn)))
-
-(defun org-make-target-link-regexp (targets)
- "Make regular expression matching all strings in TARGETS.
-The regular expression finds the targets also if there is a line break
-between words."
- (and targets
- (concat
- "\\_<\\("
- (mapconcat
- (lambda (x)
- (setq x (regexp-quote x))
- (while (string-match " +" x)
- (setq x (replace-match "\\s-+" t t x)))
- x)
- targets
- "\\|")
- "\\)\\_>")))
-
(defun org-activate-tags (limit)
- (if (re-search-forward (org-re "^\\*+.*[ \t]\\(:[[:alnum:]_@#%:]+:\\)[ \r\n]") limit t)
- (progn
- (org-remove-flyspell-overlays-in (match-beginning 1) (match-end 1))
- (add-text-properties (match-beginning 1) (match-end 1)
- (list 'mouse-face 'highlight
- 'keymap org-mouse-map))
- (org-rear-nonsticky-at (match-end 1))
- t)))
+ (when (re-search-forward
+ "^\\*+.*[ \t]\\(:[[:alnum:]_@#%:]+:\\)[ \t]*$" limit t)
+ (org-remove-flyspell-overlays-in (match-beginning 1) (match-end 1))
+ (add-text-properties (match-beginning 1) (match-end 1)
+ (list 'mouse-face 'highlight
+ 'keymap org-mouse-map))
+ (org-rear-nonsticky-at (match-end 1))
+ t))
(defun org-outline-level ()
"Compute the outline level of the heading at point.
-If this is called at a normal headline, the level is the number of stars.
-Use `org-reduced-level' to remove the effect of `org-odd-levels'."
- (save-excursion
- (if (not (condition-case nil
- (org-back-to-heading t)
- (error nil)))
- 0
- (looking-at org-outline-regexp)
- (1- (- (match-end 0) (match-beginning 0))))))
+
+If this is called at a normal headline, the level is the number
+of stars. Use `org-reduced-level' to remove the effect of
+`org-odd-levels'. Unlike to `org-current-level', this function
+takes into consideration inlinetasks."
+ (org-with-wide-buffer
+ (end-of-line)
+ (if (re-search-backward org-outline-regexp-bol nil t)
+ (1- (- (match-end 0) (match-beginning 0)))
+ 0)))
(defvar org-font-lock-keywords nil)
-(defsubst org-re-property (property &optional literal allow-null)
+(defsubst org-re-property (property &optional literal allow-null value)
"Return a regexp matching a PROPERTY line.
When optional argument LITERAL is non-nil, do not quote PROPERTY.
@@ -6166,17 +6395,25 @@ This is useful when PROPERTY is a regexp. When ALLOW-NULL is
non-nil, match properties even without a value.
Match group 3 is set to the value when it exists. If there is no
-value and ALLOW-NULL is non-nil, it is set to the empty string."
+value and ALLOW-NULL is non-nil, it is set to the empty string.
+
+With optional argument VALUE, match only property lines with
+that value; in this case, ALLOW-NULL is ignored. VALUE is quoted
+unless LITERAL is non-nil."
(concat
"^\\(?4:[ \t]*\\)"
(format "\\(?1::\\(?2:%s\\):\\)"
(if literal property (regexp-quote property)))
- (if allow-null
- "\\(?:\\(?3:$\\)\\|[ \t]+\\(?3:.*?\\)\\)\\(?5:[ \t]*\\)$"
- "[ \t]+\\(?3:[^ \r\t\n]+.*?\\)\\(?5:[ \t]*\\)$")))
+ (cond (value
+ (format "[ \t]+\\(?3:%s\\)\\(?5:[ \t]*\\)$"
+ (if literal value (regexp-quote value))))
+ (allow-null
+ "\\(?:\\(?3:$\\)\\|[ \t]+\\(?3:.*?\\)\\)\\(?5:[ \t]*\\)$")
+ (t
+ "[ \t]+\\(?3:[^ \r\t\n]+.*?\\)\\(?5:[ \t]*\\)$"))))
(defconst org-property-re
- (org-re-property ".*?" 'literal t)
+ (org-re-property "\\S-+" 'literal t)
"Regular expression matching a property line.
There are four matching groups:
1: :PROPKEY: including the leading and trailing colon,
@@ -6188,6 +6425,8 @@ There are four matching groups:
(defvar org-font-lock-hook nil
"Functions to be called for special font lock stuff.")
+(defvar org-font-lock-extra-keywords nil) ;Dynamically scoped.
+
(defvar org-font-lock-set-keywords-hook nil
"Functions that can manipulate `org-font-lock-extra-keywords'.
This is called after `org-font-lock-extra-keywords' is defined, but before
@@ -6201,7 +6440,7 @@ needs to be inserted at a specific position in the font-lock sequence.")
(defun org-set-font-lock-defaults ()
"Set font lock defaults for the current buffer."
(let* ((em org-fontify-emphasized-text)
- (lk org-activate-links)
+ (lk org-highlight-links)
(org-font-lock-extra-keywords
(list
;; Call the hook
@@ -6222,26 +6461,23 @@ needs to be inserted at a specific position in the font-lock sequence.")
'("^[ \t]*|\\( *\\([$!_^/]\\) *|.*\\)|" (1 'org-formula t))
'("| *\\(<[lrc]?[0-9]*>\\)" (1 'org-formula t))
;; Drawers
- (list org-drawer-regexp '(0 'org-special-keyword t))
- (list "^[ \t]*:END:" '(0 'org-special-keyword t))
+ '(org-fontify-drawers)
;; Properties
(list org-property-re
'(1 'org-special-keyword t)
'(3 'org-property-value t))
- ;; Links
- (if (memq 'tag lk) '(org-activate-tags (1 'org-tag prepend)))
- (if (memq 'angle lk) '(org-activate-angle-links (0 'org-link t)))
- (if (memq 'plain lk) '(org-activate-plain-links (0 'org-link t)))
- (if (memq 'bracket lk) '(org-activate-bracket-links (0 'org-link t)))
- (if (memq 'radio lk) '(org-activate-target-links (0 'org-link t)))
- (if (memq 'date lk) '(org-activate-dates (0 'org-date t)))
- (if (memq 'footnote lk) '(org-activate-footnote-links))
+ ;; Link related fontification.
+ '(org-activate-links)
+ (when (memq 'tag lk) '(org-activate-tags (1 'org-tag prepend)))
+ (when (memq 'radio lk) '(org-activate-target-links (1 'org-link t)))
+ (when (memq 'date lk) '(org-activate-dates (0 'org-date t)))
+ (when (memq 'footnote lk) '(org-activate-footnote-links))
;; Targets.
(list org-any-target-regexp '(0 'org-target t))
;; Diary sexps.
'("^&?%%(.*\\|<%%([^>\n]*?>" (0 'org-sexp-date t))
;; Macro
- '("{{{.+}}}" (0 'org-macro t))
+ '(org-fontify-macros)
'(org-hide-wide-columns (0 nil append))
;; TODO keyword
(list (format org-heading-keyword-regexp-format
@@ -6261,27 +6497,24 @@ needs to be inserted at a specific position in the font-lock sequence.")
;; Tags
'(org-font-lock-add-tag-faces)
;; Tags groups
- (if (and org-group-tags org-tag-groups-alist)
- (list (concat org-outline-regexp-bol ".+\\(:"
- (regexp-opt (mapcar 'car org-tag-groups-alist))
- ":\\).*$")
- '(1 'org-tag-group prepend)))
+ (when (and org-group-tags org-tag-groups-alist)
+ (list (concat org-outline-regexp-bol ".+\\(:"
+ (regexp-opt (mapcar 'car org-tag-groups-alist))
+ ":\\).*$")
+ '(1 'org-tag-group prepend)))
;; Special keywords
(list (concat "\\<" org-deadline-string) '(0 'org-special-keyword t))
(list (concat "\\<" org-scheduled-string) '(0 'org-special-keyword t))
(list (concat "\\<" org-closed-string) '(0 'org-special-keyword t))
(list (concat "\\<" org-clock-string) '(0 'org-special-keyword t))
;; Emphasis
- (if em
- (if (featurep 'xemacs)
- '(org-do-emphasis-faces (0 nil append))
- '(org-do-emphasis-faces)))
+ (when em '(org-do-emphasis-faces))
;; Checkboxes
'("^[ \t]*\\(?:[-+*]\\|[0-9]+[.)]\\)[ \t]+\\(?:\\[@\\(?:start:\\)?[0-9]+\\][ \t]*\\)?\\(\\[[- X]\\]\\)"
1 'org-checkbox prepend)
- (if (cdr (assq 'checkbox org-list-automatic-rules))
- '("\\[\\([0-9]*%\\)\\]\\|\\[\\([0-9]*\\)/\\([0-9]*\\)\\]"
- (0 (org-get-checkbox-statistics-face) t)))
+ (when (cdr (assq 'checkbox org-list-automatic-rules))
+ '("\\[\\([0-9]*%\\)\\]\\|\\[\\([0-9]*\\)/\\([0-9]*\\)\\]"
+ (0 (org-get-checkbox-statistics-face) t)))
;; Description list items
'("^[ \t]*[-+*][ \t]+\\(.*?[ \t]+::\\)\\([ \t]+\\|$\\)"
1 'org-list-dt prepend)
@@ -6297,83 +6530,92 @@ needs to be inserted at a specific position in the font-lock sequence.")
;; Code
'(org-activate-code (1 'org-code t))
;; COMMENT
- (list (format org-heading-keyword-regexp-format
- (concat "\\("
- org-comment-string "\\|" org-quote-string
- "\\)"))
- '(2 'org-special-keyword t))
+ (list (format
+ "^\\*+\\(?: +%s\\)?\\(?: +\\[#[A-Z0-9]\\]\\)? +\\(?9:%s\\)\\(?: \\|$\\)"
+ org-todo-regexp
+ org-comment-string)
+ '(9 'org-special-keyword t))
;; Blocks and meta lines
'(org-fontify-meta-lines-and-blocks))))
(setq org-font-lock-extra-keywords (delq nil org-font-lock-extra-keywords))
(run-hooks 'org-font-lock-set-keywords-hook)
;; Now set the full font-lock-keywords
- (org-set-local 'org-font-lock-keywords org-font-lock-extra-keywords)
- (org-set-local 'font-lock-defaults
- '(org-font-lock-keywords t nil nil backward-paragraph))
- (kill-local-variable 'font-lock-keywords) nil))
+ (setq-local org-font-lock-keywords org-font-lock-extra-keywords)
+ (setq-local font-lock-defaults
+ '(org-font-lock-keywords t nil nil backward-paragraph))
+ (kill-local-variable 'font-lock-keywords)
+ nil))
(defun org-toggle-pretty-entities ()
"Toggle the composition display of entities as UTF8 characters."
(interactive)
- (org-set-local 'org-pretty-entities (not org-pretty-entities))
+ (setq-local org-pretty-entities (not org-pretty-entities))
(org-restart-font-lock)
(if org-pretty-entities
(message "Entities are now displayed as UTF8 characters")
(save-restriction
(widen)
- (org-decompose-region (point-min) (point-max))
+ (decompose-region (point-min) (point-max))
(message "Entities are now displayed as plain text"))))
-(defvar org-custom-properties-overlays nil
+(defvar-local org-custom-properties-overlays nil
"List of overlays used for custom properties.")
-(make-variable-buffer-local 'org-custom-properties-overlays)
(defun org-toggle-custom-properties-visibility ()
"Display or hide properties in `org-custom-properties'."
(interactive)
(if org-custom-properties-overlays
- (progn (mapc 'delete-overlay org-custom-properties-overlays)
+ (progn (mapc #'delete-overlay org-custom-properties-overlays)
(setq org-custom-properties-overlays nil))
- (unless (not org-custom-properties)
- (save-excursion
- (save-restriction
- (widen)
- (goto-char (point-min))
- (while (re-search-forward org-property-re nil t)
- (mapc (lambda(p)
- (when (equal p (substring (match-string 1) 1 -1))
- (let ((o (make-overlay (match-beginning 0) (1+ (match-end 0)))))
- (overlay-put o 'invisible t)
- (overlay-put o 'org-custom-property t)
- (push o org-custom-properties-overlays))))
- org-custom-properties)))))))
+ (when org-custom-properties
+ (org-with-wide-buffer
+ (goto-char (point-min))
+ (let ((regexp (org-re-property (regexp-opt org-custom-properties) t t)))
+ (while (re-search-forward regexp nil t)
+ (let ((end (cdr (save-match-data (org-get-property-block)))))
+ (when (and end (< (point) end))
+ ;; Hide first custom property in current drawer.
+ (let ((o (make-overlay (match-beginning 0) (1+ (match-end 0)))))
+ (overlay-put o 'invisible t)
+ (overlay-put o 'org-custom-property t)
+ (push o org-custom-properties-overlays))
+ ;; Hide additional custom properties in the same drawer.
+ (while (re-search-forward regexp end t)
+ (let ((o (make-overlay (match-beginning 0) (1+ (match-end 0)))))
+ (overlay-put o 'invisible t)
+ (overlay-put o 'org-custom-property t)
+ (push o org-custom-properties-overlays)))))
+ ;; Each entry is limited to a single property drawer.
+ (outline-next-heading)))))))
(defun org-fontify-entities (limit)
"Find an entity to fontify."
(let (ee)
(when org-pretty-entities
(catch 'match
+ ;; "\_ "-family is left out on purpose. Only the first one,
+ ;; i.e., "\_ ", could be fontified anyway, and it would be
+ ;; confusing when adding a second white space character.
(while (re-search-forward
"\\\\\\(there4\\|sup[123]\\|frac[13][24]\\|[a-zA-Z]+\\)\\($\\|{}\\|[^[:alpha:]\n]\\)"
limit t)
- (if (and (not (org-in-indented-comment-line))
- (setq ee (org-entity-get (match-string 1)))
- (= (length (nth 6 ee)) 1))
- (let*
- ((end (if (equal (match-string 2) "{}")
+ (when (and (not (org-at-comment-p))
+ (setq ee (org-entity-get (match-string 1)))
+ (= (length (nth 6 ee)) 1))
+ (let* ((end (if (equal (match-string 2) "{}")
(match-end 2)
(match-end 1))))
- (add-text-properties
- (match-beginning 0) end
- (list 'font-lock-fontified t))
- (compose-region (match-beginning 0) end
- (nth 6 ee) nil)
- (backward-char 1)
- (throw 'match t))))
+ (add-text-properties
+ (match-beginning 0) end
+ (list 'font-lock-fontified t))
+ (compose-region (match-beginning 0) end
+ (nth 6 ee) nil)
+ (backward-char 1)
+ (throw 'match t))))
nil))))
(defun org-fontify-like-in-org-mode (s &optional odd-levels)
- "Fontify string S like in Org-mode."
+ "Fontify string S like in Org mode."
(with-temp-buffer
(insert s)
(let ((org-odd-levels-only odd-levels))
@@ -6387,33 +6629,55 @@ needs to be inserted at a specific position in the font-lock sequence.")
(defun org-get-level-face (n)
"Get the right face for match N in font-lock matching of headlines."
(setq org-l (- (match-end 2) (match-beginning 1) 1))
- (if org-odd-levels-only (setq org-l (1+ (/ org-l 2))))
+ (when org-odd-levels-only (setq org-l (1+ (/ org-l 2))))
(if org-cycle-level-faces
(setq org-f (nth (% (1- org-l) org-n-level-faces) org-level-faces))
(setq org-f (nth (1- (min org-l org-n-level-faces)) org-level-faces)))
(cond
((eq n 1) (if org-hide-leading-stars 'org-hide org-f))
((eq n 2) org-f)
- (t (if org-level-color-stars-only nil org-f))))
+ (t (unless org-level-color-stars-only org-f))))
+(defun org-face-from-face-or-color (context inherit face-or-color)
+ "Create a face list that inherits INHERIT, but sets the foreground color.
+When FACE-OR-COLOR is not a string, just return it."
+ (if (stringp face-or-color)
+ (list :inherit inherit
+ (cdr (assoc context org-faces-easy-properties))
+ face-or-color)
+ face-or-color))
(defun org-get-todo-face (kwd)
"Get the right face for a TODO keyword KWD.
If KWD is a number, get the corresponding match group."
- (if (numberp kwd) (setq kwd (match-string kwd)))
+ (when (numberp kwd) (setq kwd (match-string kwd)))
(or (org-face-from-face-or-color
'todo 'org-todo (cdr (assoc kwd org-todo-keyword-faces)))
(and (member kwd org-done-keywords) 'org-done)
'org-todo))
-(defun org-face-from-face-or-color (context inherit face-or-color)
- "Create a face list that inherits INHERIT, but sets the foreground color.
-When FACE-OR-COLOR is not a string, just return it."
- (if (stringp face-or-color)
- (list :inherit inherit
- (cdr (assoc context org-faces-easy-properties))
- face-or-color)
- face-or-color))
+(defun org-get-priority-face (priority)
+ "Get the right face for PRIORITY.
+PRIORITY is a character."
+ (or (org-face-from-face-or-color
+ 'priority 'org-priority (cdr (assq priority org-priority-faces)))
+ 'org-priority))
+
+(defun org-get-tag-face (tag)
+ "Get the right face for TAG.
+If TAG is a number, get the corresponding match group."
+ (let ((tag (if (wholenump tag) (match-string tag) tag)))
+ (or (org-face-from-face-or-color
+ 'tag 'org-tag (cdr (assoc tag org-tag-faces)))
+ 'org-tag)))
+
+(defun org-font-lock-add-priority-faces (limit)
+ "Add the special priority faces."
+ (while (re-search-forward "^\\*+ .*?\\(\\[#\\(.\\)\\]\\)" limit t)
+ (add-text-properties
+ (match-beginning 1) (match-end 1)
+ (list 'face (org-get-priority-face (string-to-char (match-string 2)))
+ 'font-lock-fontified t))))
(defun org-font-lock-add-tag-faces (limit)
"Add the special tag faces."
@@ -6424,39 +6688,18 @@ When FACE-OR-COLOR is not a string, just return it."
'font-lock-fontified t))
(backward-char 1))))
-(defun org-font-lock-add-priority-faces (limit)
- "Add the special priority faces."
- (while (re-search-forward "\\[#\\([A-Z0-9]\\)\\]" limit t)
- (when (save-match-data (org-at-heading-p))
- (add-text-properties
- (match-beginning 0) (match-end 0)
- (list 'face (or (org-face-from-face-or-color
- 'priority 'org-priority
- (cdr (assoc (char-after (match-beginning 1))
- org-priority-faces)))
- 'org-priority)
- 'font-lock-fontified t)))))
-
-(defun org-get-tag-face (kwd)
- "Get the right face for a TODO keyword KWD.
-If KWD is a number, get the corresponding match group."
- (if (numberp kwd) (setq kwd (match-string kwd)))
- (or (org-face-from-face-or-color
- 'tag 'org-tag (cdr (assoc kwd org-tag-faces)))
- 'org-tag))
-
-(defun org-unfontify-region (beg end &optional maybe_loudly)
+(defun org-unfontify-region (beg end &optional _maybe_loudly)
"Remove fontification and activation overlays from links."
(font-lock-default-unfontify-region beg end)
(let* ((buffer-undo-list t)
(inhibit-read-only t) (inhibit-point-motion-hooks t)
(inhibit-modification-hooks t)
deactivate-mark buffer-file-name buffer-file-truename)
- (org-decompose-region beg end)
+ (decompose-region beg end)
(remove-text-properties beg end
'(mouse-face t keymap t org-linked-text t
invisible t intangible t
- org-no-flyspell t org-emphasis t))
+ org-emphasis t))
(org-remove-font-lock-display-properties beg end)))
(defconst org-script-display '(((raise -0.3) (height 0.7))
@@ -6473,59 +6716,56 @@ and subscripts."
(while (< beg end)
(setq next (next-single-property-change beg 'display nil end)
prop (get-text-property beg 'display))
- (if (member prop org-script-display)
- (put-text-property beg next 'display nil))
+ (when (member prop org-script-display)
+ (put-text-property beg next 'display nil))
(setq beg next))))
(defun org-raise-scripts (limit)
"Add raise properties to sub/superscripts."
- (when (and org-pretty-entities org-pretty-entities-include-sub-superscripts)
- (if (re-search-forward
- (if (eq org-use-sub-superscripts t)
- org-match-substring-regexp
- org-match-substring-with-braces-regexp)
- limit t)
- (let* ((pos (point)) table-p comment-p
- (mpos (match-beginning 3))
- (emph-p (get-text-property mpos 'org-emphasis))
- (link-p (get-text-property mpos 'mouse-face))
- (keyw-p (eq 'org-special-keyword (get-text-property mpos 'face))))
- (goto-char (point-at-bol))
- (setq table-p (org-looking-at-p org-table-dataline-regexp)
- comment-p (org-looking-at-p "^[ \t]*#[ +]"))
- (goto-char pos)
- ;; Handle a_b^c
- (if (member (char-after) '(?_ ?^)) (goto-char (1- pos)))
- (if (or comment-p emph-p link-p keyw-p)
- t
- (put-text-property (match-beginning 3) (match-end 0)
- 'display
- (if (equal (char-after (match-beginning 2)) ?^)
- (nth (if table-p 3 1) org-script-display)
- (nth (if table-p 2 0) org-script-display)))
- (add-text-properties (match-beginning 2) (match-end 2)
- (list 'invisible t
- 'org-dwidth t 'org-dwidth-n 1))
- (if (and (eq (char-after (match-beginning 3)) ?{)
- (eq (char-before (match-end 3)) ?}))
- (progn
- (add-text-properties
- (match-beginning 3) (1+ (match-beginning 3))
- (list 'invisible t 'org-dwidth t 'org-dwidth-n 1))
- (add-text-properties
- (1- (match-end 3)) (match-end 3)
- (list 'invisible t 'org-dwidth t 'org-dwidth-n 1))))
- t)))))
+ (when (and org-pretty-entities org-pretty-entities-include-sub-superscripts
+ (re-search-forward
+ (if (eq org-use-sub-superscripts t)
+ org-match-substring-regexp
+ org-match-substring-with-braces-regexp)
+ limit t))
+ (let* ((pos (point)) table-p comment-p
+ (mpos (match-beginning 3))
+ (emph-p (get-text-property mpos 'org-emphasis))
+ (link-p (get-text-property mpos 'mouse-face))
+ (keyw-p (eq 'org-special-keyword (get-text-property mpos 'face))))
+ (goto-char (point-at-bol))
+ (setq table-p (looking-at-p org-table-dataline-regexp)
+ comment-p (looking-at-p "^[ \t]*#[ +]"))
+ (goto-char pos)
+ ;; Handle a_b^c
+ (when (member (char-after) '(?_ ?^)) (goto-char (1- pos)))
+ (unless (or comment-p emph-p link-p keyw-p)
+ (put-text-property (match-beginning 3) (match-end 0)
+ 'display
+ (if (equal (char-after (match-beginning 2)) ?^)
+ (nth (if table-p 3 1) org-script-display)
+ (nth (if table-p 2 0) org-script-display)))
+ (add-text-properties (match-beginning 2) (match-end 2)
+ (list 'invisible t
+ 'org-dwidth t 'org-dwidth-n 1))
+ (if (and (eq (char-after (match-beginning 3)) ?{)
+ (eq (char-before (match-end 3)) ?}))
+ (progn
+ (add-text-properties
+ (match-beginning 3) (1+ (match-beginning 3))
+ (list 'invisible t 'org-dwidth t 'org-dwidth-n 1))
+ (add-text-properties
+ (1- (match-end 3)) (match-end 3)
+ (list 'invisible t 'org-dwidth t 'org-dwidth-n 1)))))
+ t)))
;;;; Visibility cycling, including org-goto and indirect buffer
;;; Cycling
-(defvar org-cycle-global-status nil)
-(make-variable-buffer-local 'org-cycle-global-status)
+(defvar-local org-cycle-global-status nil)
(put 'org-cycle-global-status 'org-state t)
-(defvar org-cycle-subtree-status nil)
-(make-variable-buffer-local 'org-cycle-subtree-status)
+(defvar-local org-cycle-subtree-status nil)
(put 'org-cycle-subtree-status 'org-state t)
(defvar org-inlinetask-min-level)
@@ -6537,52 +6777,58 @@ and subscripts."
;;;###autoload
(defun org-cycle (&optional arg)
- "TAB-action and visibility cycling for Org-mode.
+ "TAB-action and visibility cycling for Org mode.
-This is the command invoked in Org-mode by the TAB key. Its main purpose
-is outline visibility cycling, but it also invokes other actions
+This is the command invoked in Org mode by the `TAB' key. Its main
+purpose is outline visibility cycling, but it also invokes other actions
in special contexts.
-- When this function is called with a prefix argument, rotate the entire
- buffer through 3 states (global cycling)
+When this function is called with a `\\[universal-argument]' prefix, rotate \
+the entire
+buffer through 3 states (global cycling)
1. OVERVIEW: Show only top-level headlines.
2. CONTENTS: Show all headlines of all levels, but no body text.
3. SHOW ALL: Show everything.
- When called with two `C-u C-u' prefixes, switch to the startup visibility,
- determined by the variable `org-startup-folded', and by any VISIBILITY
- properties in the buffer.
- When called with three `C-u C-u C-u' prefixed, show the entire buffer,
- including any drawers.
-- When inside a table, re-align the table and move to the next field.
+With a `\\[universal-argument] \\[universal-argument]' prefix argument, \
+switch to the startup visibility,
+determined by the variable `org-startup-folded', and by any VISIBILITY
+properties in the buffer.
+
+With a `\\[universal-argument] \\[universal-argument] \
+\\[universal-argument]' prefix argument, show the entire buffer, including
+any drawers.
-- When point is at the beginning of a headline, rotate the subtree started
- by this line through 3 different states (local cycling)
+When inside a table, re-align the table and move to the next field.
+
+When point is at the beginning of a headline, rotate the subtree started
+by this line through 3 different states (local cycling)
1. FOLDED: Only the main headline is shown.
2. CHILDREN: The main headline and the direct children are shown.
From this state, you can move to one of the children
and zoom in further.
3. SUBTREE: Show the entire subtree, including body text.
- If there is no subtree, switch directly from CHILDREN to FOLDED.
-
-- When point is at the beginning of an empty headline and the variable
- `org-cycle-level-after-item/entry-creation' is set, cycle the level
- of the headline by demoting and promoting it to likely levels. This
- speeds up creation document structure by pressing TAB once or several
- times right after creating a new headline.
-
-- When there is a numeric prefix, go up to a heading with level ARG, do
- a `show-subtree' and return to the previous cursor position. If ARG
- is negative, go up that many levels.
-
-- When point is not at the beginning of a headline, execute the global
- binding for TAB, which is re-indenting the line. See the option
- `org-cycle-emulate-tab' for details.
-
-- Special case: if point is at the beginning of the buffer and there is
- no headline in line 1, this function will act as if called with prefix arg
- (C-u TAB, same as S-TAB) also when called without prefix arg.
- But only if also the variable `org-cycle-global-at-bob' is t."
+If there is no subtree, switch directly from CHILDREN to FOLDED.
+
+When point is at the beginning of an empty headline and the variable
+`org-cycle-level-after-item/entry-creation' is set, cycle the level
+of the headline by demoting and promoting it to likely levels. This
+speeds up creation document structure by pressing `TAB' once or several
+times right after creating a new headline.
+
+When there is a numeric prefix, go up to a heading with level ARG, do
+a `show-subtree' and return to the previous cursor position. If ARG
+is negative, go up that many levels.
+
+When point is not at the beginning of a headline, execute the global
+binding for `TAB', which is re-indenting the line. See the option
+`org-cycle-emulate-tab' for details.
+
+As a special case, if point is at the beginning of the buffer and there is
+no headline in line 1, this function will act as if called with prefix arg
+\(`\\[universal-argument] TAB', same as `S-TAB') also when called without \
+prefix arg, but only
+if the variable `org-cycle-global-at-bob' is t."
(interactive "P")
(org-load-modules-maybe)
(unless (or (run-hook-with-args-until-success 'org-tab-first-hook)
@@ -6611,10 +6857,6 @@ in special contexts.
org-cycle-hook))
(pos (point)))
- (if (or bob-special (equal arg '(4)))
- ;; special case: use global cycling
- (setq arg t))
-
(cond
((equal arg '(16))
@@ -6623,32 +6865,36 @@ in special contexts.
(org-unlogged-message "Startup visibility, plus VISIBILITY properties"))
((equal arg '(64))
- (show-all)
+ (outline-show-all)
(org-unlogged-message "Entire buffer visible, including drawers"))
+ ((equal arg '(4)) (org-cycle-internal-global))
+
+ ;; Try hiding block at point.
+ ((org-hide-block-toggle-maybe))
+
;; Try cdlatex TAB completion
((org-try-cdlatex-tab))
;; Table: enter it or move to the next field.
((org-at-table-p 'any)
(if (org-at-table.el-p)
- (message "%s" "Use C-c ' to edit table.el tables")
+ (message "%s" (substitute-command-keys "\\\
+Use `\\[org-edit-special]' to edit table.el tables"))
(if arg (org-table-edit-field t)
(org-table-justify-field-maybe)
(call-interactively 'org-table-next-field))))
- ((run-hook-with-args-until-success
- 'org-tab-after-check-for-table-hook))
+ ((run-hook-with-args-until-success 'org-tab-after-check-for-table-hook))
;; Global cycling: delegate to `org-cycle-internal-global'.
- ((eq arg t) (org-cycle-internal-global))
+ (bob-special (org-cycle-internal-global))
;; Drawers: delegate to `org-flag-drawer'.
- ((and org-drawers org-drawer-regexp
- (save-excursion
- (beginning-of-line 1)
- (looking-at org-drawer-regexp)))
- (org-flag-drawer ; toggle block visibility
+ ((save-excursion
+ (beginning-of-line 1)
+ (looking-at org-drawer-regexp))
+ (org-flag-drawer ; toggle block visibility
(not (get-char-property (match-end 0) 'invisible))))
;; Show-subtree, ARG levels up from here.
@@ -6667,7 +6913,7 @@ in special contexts.
;; At an item/headline: delegate to `org-cycle-internal-local'.
((and (or (and org-cycle-include-plain-lists (org-at-item-p))
- (save-excursion (beginning-of-line 1)
+ (save-excursion (move-beginning-of-line 1)
(looking-at org-outline-regexp)))
(or (bolp) (not (eq org-cycle-emulate-tab 'exc-hl-bol))))
(org-cycle-internal-local))
@@ -6722,7 +6968,7 @@ in special contexts.
(eq org-cycle-global-status 'contents))
;; We just showed the table of contents - now show everything
(run-hook-with-args 'org-pre-cycle-hook 'all)
- (show-all)
+ (outline-show-all)
(unless ga (org-unlogged-message "SHOW ALL"))
(setq org-cycle-global-status 'all)
(run-hook-with-args 'org-cycle-hook 'all))
@@ -6738,6 +6984,11 @@ in special contexts.
(defvar org-called-with-limited-levels nil
"Non-nil when `org-with-limited-levels' is currently active.")
+(defun org-invisible-p (&optional pos)
+ "Non-nil if the character after POS is invisible.
+If POS is nil, use `point' instead."
+ (get-char-property (or pos (point)) 'invisible))
+
(defun org-cycle-internal-local ()
"Do the local cycling action."
(let ((goal-column 0) eoh eol eos has-children children-skipped struct)
@@ -6765,15 +7016,10 @@ in special contexts.
(org-list-search-forward (org-item-beginning-re) eos t)))))
;; Determine end invisible part of buffer (EOL)
(beginning-of-line 2)
- ;; XEmacs doesn't have `next-single-char-property-change'
- (if (featurep 'xemacs)
- (while (and (not (eobp)) ;; this is like `next-line'
- (get-char-property (1- (point)) 'invisible))
- (beginning-of-line 2))
- (while (and (not (eobp)) ;; this is like `next-line'
- (get-char-property (1- (point)) 'invisible))
- (goto-char (next-single-char-property-change (point) 'invisible))
- (and (eolp) (beginning-of-line 2))))
+ (while (and (not (eobp)) ;This is like `next-line'.
+ (get-char-property (1- (point)) 'invisible))
+ (goto-char (next-single-char-property-change (point) 'invisible))
+ (and (eolp) (beginning-of-line 2)))
(setq eol (point)))
;; Find out what to do next and set `this-command'
(cond
@@ -6786,7 +7032,7 @@ in special contexts.
(save-excursion
(goto-char eos)
(outline-next-heading)
- (if (outline-invisible-p) (org-flag-heading nil))))
+ (when (org-invisible-p) (org-flag-heading nil))))
((and (or (>= eol eos)
(not (string-match "\\S-" (buffer-substring eol eos))))
(or has-children
@@ -6798,7 +7044,7 @@ in special contexts.
(if (org-at-item-p)
(org-list-set-item-visibility (point-at-bol) struct 'children)
(org-show-entry)
- (org-with-limited-levels (show-children))
+ (org-with-limited-levels (org-show-children))
;; FIXME: This slows down the func way too much.
;; How keep drawers hidden in subtree anyway?
;; (when (memq 'org-cycle-hide-drawers org-cycle-hook)
@@ -6813,14 +7059,14 @@ in special contexts.
(let* ((struct (org-list-struct))
(prevs (org-list-prevs-alist struct))
(end (org-list-get-bottom-point struct)))
- (mapc (lambda (e) (org-list-set-item-visibility e struct 'folded))
- (org-list-get-all-items (point) struct prevs))
+ (dolist (e (org-list-get-all-items (point) struct prevs))
+ (org-list-set-item-visibility e struct 'folded))
(goto-char (if (< end eos) end eos)))))))
(org-unlogged-message "CHILDREN")
(save-excursion
(goto-char eos)
(outline-next-heading)
- (if (outline-invisible-p) (org-flag-heading nil)))
+ (when (org-invisible-p) (org-flag-heading nil)))
(setq org-cycle-subtree-status 'children)
(unless (org-before-first-heading-p)
(run-hook-with-args 'org-cycle-hook 'children)))
@@ -6849,15 +7095,15 @@ in special contexts.
;;;###autoload
(defun org-global-cycle (&optional arg)
"Cycle the global visibility. For details see `org-cycle'.
-With \\[universal-argument] prefix arg, switch to startup visibility.
+With `\\[universal-argument]' prefix ARG, switch to startup visibility.
With a numeric prefix, show all headlines up to that level."
(interactive "P")
(let ((org-cycle-include-plain-lists
(if (derived-mode-p 'org-mode) org-cycle-include-plain-lists nil)))
(cond
((integerp arg)
- (show-all)
- (hide-sublevels arg)
+ (outline-show-all)
+ (outline-hide-sublevels arg)
(setq org-cycle-global-status 'contents))
((equal arg '(4))
(org-set-startup-visibility)
@@ -6874,9 +7120,9 @@ With a numeric prefix, show all headlines up to that level."
(org-content))
((or (eq org-startup-folded 'showeverything)
(eq org-startup-folded nil))
- (show-all)))
+ (outline-show-all)))
(unless (eq org-startup-folded 'showeverything)
- (if org-hide-block-startup (org-hide-block-all))
+ (when org-hide-block-startup (org-hide-block-all))
(org-set-visibility-according-to-property 'no-cleanup)
(org-cycle-hide-archived-subtrees 'all)
(org-cycle-hide-drawers 'all)
@@ -6885,34 +7131,32 @@ With a numeric prefix, show all headlines up to that level."
(defun org-set-visibility-according-to-property (&optional no-cleanup)
"Switch subtree visibilities according to :VISIBILITY: property."
(interactive)
- (let (org-show-entry-below state)
- (save-excursion
- (goto-char (point-min))
- (while (re-search-forward
- "^[ \t]*:VISIBILITY:[ \t]+\\([a-z]+\\)"
- nil t)
- (setq state (match-string 1))
- (save-excursion
- (org-back-to-heading t)
- (hide-subtree)
- (org-reveal)
- (cond
- ((equal state '("fold" "folded"))
- (hide-subtree))
- ((equal state "children")
- (org-show-hidden-entry)
- (show-children))
- ((equal state "content")
- (save-excursion
- (save-restriction
- (org-narrow-to-subtree)
- (org-content))))
- ((member state '("all" "showall"))
- (show-subtree)))))
- (unless no-cleanup
- (org-cycle-hide-archived-subtrees 'all)
- (org-cycle-hide-drawers 'all)
- (org-cycle-show-empty-lines 'all)))))
+ (org-with-wide-buffer
+ (goto-char (point-min))
+ (while (re-search-forward "^[ \t]*:VISIBILITY:" nil t)
+ (if (not (org-at-property-p)) (outline-next-heading)
+ (let ((state (match-string 3)))
+ (save-excursion
+ (org-back-to-heading t)
+ (outline-hide-subtree)
+ (org-reveal)
+ (cond
+ ((equal state "folded")
+ (outline-hide-subtree))
+ ((equal state "children")
+ (org-show-hidden-entry)
+ (org-show-children))
+ ((equal state "content")
+ (save-excursion
+ (save-restriction
+ (org-narrow-to-subtree)
+ (org-content))))
+ ((member state '("all" "showall"))
+ (outline-show-subtree)))))))
+ (unless no-cleanup
+ (org-cycle-hide-archived-subtrees 'all)
+ (org-cycle-hide-drawers 'all)
+ (org-cycle-show-empty-lines 'all))))
;; This function uses outline-regexp instead of the more fundamental
;; org-outline-regexp so that org-cycle-global works outside of Org
@@ -6928,11 +7172,10 @@ results."
(let ((level
(save-excursion
(goto-char (point-min))
- (if (re-search-forward (concat "^" outline-regexp) nil t)
- (progn
- (goto-char (match-beginning 0))
- (funcall outline-level))))))
- (and level (hide-sublevels level)))))
+ (when (re-search-forward (concat "^" outline-regexp) nil t)
+ (goto-char (match-beginning 0))
+ (funcall outline-level)))))
+ (and level (outline-hide-sublevels level)))))
(defun org-content (&optional arg)
"Show all headlines in the buffer, like a table of contents.
@@ -6950,9 +7193,9 @@ With numerical argument N, show content up to level N."
t)
(looking-at org-outline-regexp))
(if (integerp arg)
- (show-children (1- arg))
- (show-branches))
- (if (bobp) (throw 'exit nil))))))
+ (org-show-children (1- arg))
+ (outline-show-branches))
+ (when (bobp) (throw 'exit nil))))))
(defun org-optimize-window-after-visibility-change (state)
"Adjust the window after a change in outline visibility.
@@ -6967,13 +7210,11 @@ This function is the default value of the hook `org-cycle-hook'."
(defun org-remove-empty-overlays-at (pos)
"Remove outline overlays that do not contain non-white stuff."
- (mapc
- (lambda (o)
- (and (eq 'outline (overlay-get o 'invisible))
- (not (string-match "\\S-" (buffer-substring (overlay-start o)
- (overlay-end o))))
- (delete-overlay o)))
- (overlays-at pos)))
+ (dolist (o (overlays-at pos))
+ (and (eq 'outline (overlay-get o 'invisible))
+ (not (string-match "\\S-" (buffer-substring (overlay-start o)
+ (overlay-end o))))
+ (delete-overlay o))))
(defun org-clean-visibility-after-subtree-move ()
"Fix visibility issues after moving a subtree."
@@ -6991,7 +7232,7 @@ This function is the default value of the hook `org-cycle-hook'."
(point-at-eol)
(point))))
(level (looking-at "\\*+"))
- (re (if level (concat "^" (regexp-quote (match-string 0)) " "))))
+ (re (when level (concat "^" (regexp-quote (match-string 0)) " "))))
(save-excursion
(save-restriction
(narrow-to-region beg end)
@@ -6999,10 +7240,10 @@ This function is the default value of the hook `org-cycle-hook'."
;; Properly fold already folded siblings
(goto-char (point-min))
(while (re-search-forward re nil t)
- (if (and (not (outline-invisible-p))
- (save-excursion
- (goto-char (point-at-eol)) (outline-invisible-p)))
- (hide-entry))))
+ (when (and (not (org-invisible-p))
+ (save-excursion
+ (goto-char (point-at-eol)) (org-invisible-p)))
+ (outline-hide-entry))))
(org-cycle-show-empty-lines 'overview)
(org-cycle-hide-drawers 'overview)))))
@@ -7012,7 +7253,7 @@ The region to be covered depends on STATE when called through
`org-cycle-hook'. Lisp program can use t for STATE to get the
entire buffer covered. Note that an empty line is only shown if there
are at least `org-cycle-separator-lines' empty lines before the headline."
- (when (not (= org-cycle-separator-lines 0))
+ (when (/= org-cycle-separator-lines 0)
(save-excursion
(let* ((n (abs org-cycle-separator-lines))
(re (cond
@@ -7021,38 +7262,34 @@ are at least `org-cycle-separator-lines' empty lines before the headline."
(t (let ((ns (number-to-string (- n 2))))
(concat "^\\(?:[ \t]*\n\\)\\{" ns "," ns "\\}"
"[ \t]*\\(\n[ \t]*\n\\*+\\) ")))))
- beg end b e)
+ beg end)
(cond
((memq state '(overview contents t))
(setq beg (point-min) end (point-max)))
((memq state '(children folded))
- (setq beg (point) end (progn (org-end-of-subtree t t)
- (beginning-of-line 2)
- (point)))))
+ (setq beg (point)
+ end (progn (org-end-of-subtree t t)
+ (line-beginning-position 2)))))
(when beg
(goto-char beg)
(while (re-search-forward re end t)
(unless (get-char-property (match-end 1) 'invisible)
- (setq e (match-end 1))
- (if (< org-cycle-separator-lines 0)
- (setq b (save-excursion
- (goto-char (match-beginning 0))
- (org-back-over-empty-lines)
- (if (save-excursion
- (goto-char (max (point-min) (1- (point))))
- (org-at-heading-p))
- (1- (point))
- (point))))
- (setq b (match-beginning 1)))
- (outline-flag-region b e nil)))))))
+ (let ((e (match-end 1))
+ (b (if (>= org-cycle-separator-lines 0)
+ (match-beginning 1)
+ (save-excursion
+ (goto-char (match-beginning 0))
+ (skip-chars-backward " \t\n")
+ (line-end-position)))))
+ (outline-flag-region b e nil))))))))
;; Never hide empty lines at the end of the file.
(save-excursion
(goto-char (point-max))
(outline-previous-heading)
(outline-end-of-heading)
- (if (and (looking-at "[ \t\n]+")
- (= (match-end 0) (point-max)))
- (outline-flag-region (point) (match-end 0) nil))))
+ (when (and (looking-at "[ \t\n]+")
+ (= (match-end 0) (point-max)))
+ (outline-flag-region (point) (match-end 0) nil))))
(defun org-show-empty-lines-in-parent ()
"Move to the parent and re-show empty lines before visible headlines."
@@ -7061,28 +7298,28 @@ are at least `org-cycle-separator-lines' empty lines before the headline."
(org-cycle-show-empty-lines context))))
(defun org-files-list ()
- "Return `org-agenda-files' list, plus all open org-mode files.
+ "Return `org-agenda-files' list, plus all open Org files.
This is useful for operations that need to scan all of a user's
open and agenda-wise Org files."
(let ((files (mapcar 'expand-file-name (org-agenda-files))))
(dolist (buf (buffer-list))
(with-current-buffer buf
- (if (and (derived-mode-p 'org-mode) (buffer-file-name))
- (let ((file (expand-file-name (buffer-file-name))))
- (unless (member file files)
- (push file files))))))
+ (when (and (derived-mode-p 'org-mode) (buffer-file-name))
+ (cl-pushnew (expand-file-name (buffer-file-name)) files))))
files))
(defsubst org-entry-beginning-position ()
"Return the beginning position of the current entry."
- (save-excursion (outline-back-to-heading t) (point)))
+ (save-excursion (org-back-to-heading t) (point)))
(defsubst org-entry-end-position ()
"Return the end position of the current entry."
(save-excursion (outline-next-heading) (point)))
-(defun org-cycle-hide-drawers (state)
- "Re-hide all drawers after a visibility state change."
+(defun org-cycle-hide-drawers (state &optional exceptions)
+ "Re-hide all drawers after a visibility state change.
+When non-nil, optional argument EXCEPTIONS is a list of strings
+specifying which drawers should not be hidden."
(when (and (derived-mode-p 'org-mode)
(not (memq state '(overview folded contents))))
(save-excursion
@@ -7093,36 +7330,39 @@ open and agenda-wise Org files."
(save-excursion (outline-next-heading) (point))
(org-end-of-subtree t)))))
(goto-char beg)
- (while (re-search-forward org-drawer-regexp end t)
- (org-flag-drawer t))))))
-
-(defun org-cycle-hide-inline-tasks (state)
- "Re-hide inline tasks when switching to `contents' or `children'
-visibility state."
- (case state
- (contents
- (when (org-bound-and-true-p org-inlinetask-min-level)
- (hide-sublevels (1- org-inlinetask-min-level))))
- (children
- (when (featurep 'org-inlinetask)
- (save-excursion
- (while (and (outline-next-heading)
- (org-inlinetask-at-task-p))
- (org-inlinetask-toggle-visibility)
- (org-inlinetask-goto-end)))))))
-
-(defun org-flag-drawer (flag)
- "When FLAG is non-nil, hide the drawer we are within.
-Otherwise make it visible."
- (save-excursion
- (beginning-of-line 1)
- (when (looking-at "^[ \t]*:[a-zA-Z][a-zA-Z0-9]*:")
- (let ((b (match-end 0)))
- (if (re-search-forward
- "^[ \t]*:END:"
- (save-excursion (outline-next-heading) (point)) t)
- (outline-flag-region b (point-at-eol) flag)
- (user-error ":END: line missing at position %s" b))))))
+ (while (re-search-forward org-drawer-regexp (max end (point)) t)
+ (unless (member-ignore-case (match-string 1) exceptions)
+ (let ((drawer (org-element-at-point)))
+ (when (memq (org-element-type drawer) '(drawer property-drawer))
+ (org-flag-drawer t drawer)
+ ;; Make sure to skip drawer entirely or we might flag
+ ;; it another time when matching its ending line with
+ ;; `org-drawer-regexp'.
+ (goto-char (org-element-property :end drawer))))))))))
+
+(defun org-flag-drawer (flag &optional element)
+ "When FLAG is non-nil, hide the drawer we are at.
+Otherwise make it visible. When optional argument ELEMENT is
+a parsed drawer, as returned by `org-element-at-point', hide or
+show that drawer instead."
+ (let ((drawer (or element
+ (and (save-excursion
+ (beginning-of-line)
+ (looking-at-p org-drawer-regexp))
+ (org-element-at-point)))))
+ (when (memq (org-element-type drawer) '(drawer property-drawer))
+ (let ((post (org-element-property :post-affiliated drawer)))
+ (save-excursion
+ (outline-flag-region
+ (progn (goto-char post) (line-end-position))
+ (progn (goto-char (org-element-property :end drawer))
+ (skip-chars-backward " \r\t\n")
+ (line-end-position))
+ flag))
+ ;; When the drawer is hidden away, make sure point lies in
+ ;; a visible part of the buffer.
+ (when (and flag (> (line-beginning-position) post))
+ (goto-char post))))))
(defun org-subtree-end-visible-p ()
"Is the end of the current subtree visible?"
@@ -7131,9 +7371,11 @@ Otherwise make it visible."
(defun org-first-headline-recenter ()
"Move cursor to the first headline and recenter the headline."
- (goto-char (point-min))
- (when (re-search-forward (concat "^\\(" org-outline-regexp "\\)") nil t)
- (set-window-start (selected-window) (point-at-bol))))
+ (let ((window (get-buffer-window)))
+ (when window
+ (goto-char (point-min))
+ (when (re-search-forward (concat "^\\(" org-outline-regexp "\\)") nil t)
+ (set-window-start window (line-beginning-position))))))
;;; Saving and restoring visibility
@@ -7144,38 +7386,30 @@ The return value is a list of cons cells, with start and stop
positions for each overlay.
If USE-MARKERS is set, return the positions as markers."
(let (beg end)
- (save-excursion
- (save-restriction
- (widen)
- (delq nil
- (mapcar (lambda (o)
- (when (eq (overlay-get o 'invisible) 'outline)
- (setq beg (overlay-start o)
- end (overlay-end o))
- (and beg end (> end beg)
- (if use-markers
- (cons (move-marker (make-marker) beg)
- (move-marker (make-marker) end))
- (cons beg end)))))
- (overlays-in (point-min) (point-max))))))))
+ (org-with-wide-buffer
+ (delq nil
+ (mapcar (lambda (o)
+ (when (eq (overlay-get o 'invisible) 'outline)
+ (setq beg (overlay-start o)
+ end (overlay-end o))
+ (and beg end (> end beg)
+ (if use-markers
+ (cons (copy-marker beg)
+ (copy-marker end t))
+ (cons beg end)))))
+ (overlays-in (point-min) (point-max)))))))
(defun org-set-outline-overlay-data (data)
"Create visibility overlays for all positions in DATA.
DATA should have been made by `org-outline-overlay-data'."
- (let (o)
- (save-excursion
- (save-restriction
- (widen)
- (show-all)
- (mapc (lambda (c)
- (outline-flag-region (car c) (cdr c) t))
- data)))))
+ (org-with-wide-buffer
+ (outline-show-all)
+ (dolist (c data) (outline-flag-region (car c) (cdr c) t))))
;;; Folding of blocks
-(defvar org-hide-block-overlays nil
+(defvar-local org-hide-block-overlays nil
"Overlays hiding blocks.")
-(make-variable-buffer-local 'org-hide-block-overlays)
(defun org-block-map (function &optional start end)
"Call FUNCTION at the head of all source blocks in the current buffer.
@@ -7192,74 +7426,85 @@ Optional arguments START and END can be used to limit the range."
(defun org-hide-block-toggle-all ()
"Toggle the visibility of all blocks in the current buffer."
- (org-block-map #'org-hide-block-toggle))
+ (org-block-map 'org-hide-block-toggle))
(defun org-hide-block-all ()
"Fold all blocks in the current buffer."
(interactive)
(org-show-block-all)
- (org-block-map #'org-hide-block-toggle-maybe))
+ (org-block-map 'org-hide-block-toggle-maybe))
(defun org-show-block-all ()
"Unfold all blocks in the current buffer."
(interactive)
- (mapc 'delete-overlay org-hide-block-overlays)
+ (mapc #'delete-overlay org-hide-block-overlays)
(setq org-hide-block-overlays nil))
(defun org-hide-block-toggle-maybe ()
- "Toggle visibility of block at point."
+ "Toggle visibility of block at point.
+Unlike to `org-hide-block-toggle', this function does not throw
+an error. Return a non-nil value when toggling is successful."
(interactive)
- (let ((case-fold-search t))
- (if (save-excursion
- (beginning-of-line 1)
- (looking-at org-block-regexp))
- (progn (org-hide-block-toggle)
- t) ;; to signal that we took action
- nil))) ;; to signal that we did not
+ (ignore-errors (org-hide-block-toggle)))
(defun org-hide-block-toggle (&optional force)
- "Toggle the visibility of the current block."
+ "Toggle the visibility of the current block.
+When optional argument FORCE is `off', make block visible. If it
+is non-nil, hide it unconditionally. Throw an error when not at
+a block. Return a non-nil value when toggling is successful."
(interactive)
- (save-excursion
- (beginning-of-line)
- (if (re-search-forward org-block-regexp nil t)
- (let ((start (- (match-beginning 4) 1)) ;; beginning of body
- (end (match-end 0)) ;; end of entire body
- ov)
- (if (memq t (mapcar (lambda (overlay)
- (eq (overlay-get overlay 'invisible)
- 'org-hide-block))
- (overlays-at start)))
- (if (or (not force) (eq force 'off))
- (mapc (lambda (ov)
- (when (member ov org-hide-block-overlays)
- (setq org-hide-block-overlays
- (delq ov org-hide-block-overlays)))
- (when (eq (overlay-get ov 'invisible)
- 'org-hide-block)
- (delete-overlay ov)))
- (overlays-at start)))
- (setq ov (make-overlay start end))
- (overlay-put ov 'invisible 'org-hide-block)
- ;; make the block accessible to isearch
- (overlay-put
- ov 'isearch-open-invisible
- (lambda (ov)
- (when (member ov org-hide-block-overlays)
- (setq org-hide-block-overlays
- (delq ov org-hide-block-overlays)))
- (when (eq (overlay-get ov 'invisible)
- 'org-hide-block)
- (delete-overlay ov))))
- (push ov org-hide-block-overlays)))
- (user-error "Not looking at a source block"))))
-
-;; org-tab-after-check-for-cycling-hook
-(add-hook 'org-tab-first-hook 'org-hide-block-toggle-maybe)
+ (let ((element (org-element-at-point)))
+ (unless (memq (org-element-type element)
+ '(center-block comment-block dynamic-block example-block
+ export-block quote-block special-block
+ src-block verse-block))
+ (user-error "Not at a block"))
+ (let* ((start (save-excursion
+ (goto-char (org-element-property :post-affiliated element))
+ (line-end-position)))
+ (end (save-excursion
+ (goto-char (org-element-property :end element))
+ (skip-chars-backward " \r\t\n")
+ (line-end-position)))
+ (overlays (overlays-at start)))
+ (cond
+ ;; Do nothing when not before or at the block opening line or
+ ;; at the block closing line.
+ ((let ((eol (line-end-position))) (and (> eol start) (/= eol end))) nil)
+ ((and (not (eq force 'off))
+ (not (memq t (mapcar
+ (lambda (o)
+ (eq (overlay-get o 'invisible) 'org-hide-block))
+ overlays))))
+ (let ((ov (make-overlay start end)))
+ (overlay-put ov 'invisible 'org-hide-block)
+ ;; Make the block accessible to `isearch'.
+ (overlay-put
+ ov 'isearch-open-invisible
+ (lambda (ov)
+ (when (memq ov org-hide-block-overlays)
+ (setq org-hide-block-overlays (delq ov org-hide-block-overlays)))
+ (when (eq (overlay-get ov 'invisible) 'org-hide-block)
+ (delete-overlay ov))))
+ (push ov org-hide-block-overlays)
+ ;; When the block is hidden away, make sure point is left in
+ ;; a visible part of the buffer.
+ (when (> (line-beginning-position) start)
+ (goto-char start)
+ (beginning-of-line))
+ ;; Signal successful toggling.
+ t))
+ ((or (not force) (eq force 'off))
+ (dolist (ov overlays t)
+ (when (memq ov org-hide-block-overlays)
+ (setq org-hide-block-overlays (delq ov org-hide-block-overlays)))
+ (when (eq (overlay-get ov 'invisible) 'org-hide-block)
+ (delete-overlay ov))))))))
+
;; Remove overlays when changing major mode
(add-hook 'org-mode-hook
- (lambda () (org-add-hook 'change-major-mode-hook
- 'org-show-block-all 'append 'local)))
+ (lambda () (add-hook 'change-major-mode-hook
+ 'org-show-block-all 'append 'local)))
;;; Org-goto
@@ -7305,7 +7550,7 @@ Optional arguments START and END can be used to limit the range."
(defconst org-goto-help
"Browse buffer copy, to find location or copy text.%s
RET=jump to location C-g=quit and return to previous location
-[Up]/[Down]=next/prev headline TAB=cycle visibility [/] org-occur")
+\[Up]/[Down]=next/prev headline TAB=cycle visibility [/] org-occur")
(defvar org-goto-start-pos) ; dynamically scoped parameter
@@ -7343,23 +7588,23 @@ With a prefix argument, use the alternative interface: e.g., if
(selected-point
(if (eq interface 'outline)
(car (org-get-location (current-buffer) org-goto-help))
- (let ((pa (org-refile-get-location "Goto" nil nil t)))
+ (let ((pa (org-refile-get-location "Goto")))
(org-refile-check-position pa)
(nth 3 pa)))))
(if selected-point
(progn
(org-mark-ring-push org-goto-start-pos)
(goto-char selected-point)
- (if (or (outline-invisible-p) (org-invisible-p2))
- (org-show-context 'org-goto)))
+ (when (or (org-invisible-p) (org-invisible-p2))
+ (org-show-context 'org-goto)))
(message "Quit"))))
(defvar org-goto-selected-point nil) ; dynamically scoped parameter
(defvar org-goto-exit-command nil) ; dynamically scoped parameter
(defvar org-goto-local-auto-isearch-map) ; defined below
-(defun org-get-location (buf help)
- "Let the user select a location in the Org-mode buffer BUF.
+(defun org-get-location (_buf help)
+ "Let the user select a location in current buffer.
This function uses a recursive edit. It returns the selected position
or nil."
(org-no-popups
@@ -7372,7 +7617,7 @@ or nil."
(save-window-excursion
(delete-other-windows)
(and (get-buffer "*org-goto*") (kill-buffer "*org-goto*"))
- (org-pop-to-buffer-same-window
+ (pop-to-buffer-same-window
(condition-case nil
(make-indirect-buffer (current-buffer) "*org-goto*")
(error (make-indirect-buffer (current-buffer) "*org-goto*"))))
@@ -7390,11 +7635,9 @@ or nil."
(setq buffer-read-only t)
(if (and (boundp 'org-goto-start-pos)
(integer-or-marker-p org-goto-start-pos))
- (let ((org-show-hierarchy-above t)
- (org-show-siblings t)
- (org-show-following-heading t))
- (goto-char org-goto-start-pos)
- (and (outline-invisible-p) (org-show-context)))
+ (progn (goto-char org-goto-start-pos)
+ (when (org-invisible-p)
+ (org-show-set-visibility 'lineage)))
(goto-char (point-min)))
(let (org-special-ctrl-a/e) (org-beginning-of-line))
(message "Select location and press RET")
@@ -7405,8 +7648,14 @@ or nil."
(defvar org-goto-local-auto-isearch-map (make-sparse-keymap))
(set-keymap-parent org-goto-local-auto-isearch-map isearch-mode-map)
-(define-key org-goto-local-auto-isearch-map "\C-i" 'isearch-other-control-char)
-(define-key org-goto-local-auto-isearch-map "\C-m" 'isearch-other-control-char)
+;; `isearch-other-control-char' was removed in Emacs 24.4.
+(if (fboundp 'isearch-other-control-char)
+ (progn
+ (define-key org-goto-local-auto-isearch-map "\C-i" 'isearch-other-control-char)
+ (define-key org-goto-local-auto-isearch-map "\C-m" 'isearch-other-control-char))
+ (define-key org-goto-local-auto-isearch-map "\C-i" nil)
+ (define-key org-goto-local-auto-isearch-map "\C-m" nil)
+ (define-key org-goto-local-auto-isearch-map [return] nil))
(defun org-goto-local-search-headings (string bound noerror)
"Search and make sure that any matches are in headlines."
@@ -7414,9 +7663,12 @@ or nil."
(while (if isearch-forward
(search-forward string bound noerror)
(search-backward string bound noerror))
- (when (let ((context (mapcar 'car (save-match-data (org-context)))))
- (and (member :headline context)
- (not (member :tags context))))
+ (when (save-match-data
+ (and (save-excursion
+ (beginning-of-line)
+ (looking-at org-complex-heading-regexp))
+ (or (not (match-beginning 5))
+ (< (point) (match-beginning 5)))))
(throw 'return (point))))))
(defun org-goto-local-auto-isearch ()
@@ -7428,11 +7680,11 @@ or nil."
(isearch-mode t)
(isearch-process-search-char (string-to-char keys)))))
-(defun org-goto-ret (&optional arg)
+(defun org-goto-ret (&optional _arg)
"Finish `org-goto' by going to the new location."
(interactive "P")
- (setq org-goto-selected-point (point)
- org-goto-exit-command 'return)
+ (setq org-goto-selected-point (point))
+ (setq org-goto-exit-command 'return)
(throw 'exit nil))
(defun org-goto-left ()
@@ -7471,17 +7723,18 @@ or nil."
(defun org-tree-to-indirect-buffer (&optional arg)
"Create indirect buffer and narrow it to current subtree.
+
With a numerical prefix ARG, go up to this level and then take that tree.
If ARG is negative, go up that many levels.
If `org-indirect-buffer-display' is not `new-frame', the command removes the
indirect buffer previously made with this command, to avoid proliferation of
indirect buffers. However, when you call the command with a \
-\\[universal-argument] prefix, or
-when `org-indirect-buffer-display' is `new-frame', the last buffer
-is kept so that you can work with several indirect buffers at the same time.
-If `org-indirect-buffer-display' is `dedicated-frame', the \
-\\[universal-argument] prefix also
+`\\[universal-argument]' prefix, or
+when `org-indirect-buffer-display' is `new-frame', the last buffer is kept
+so that you can work with several indirect buffers at the same time. If
+`org-indirect-buffer-display' is `dedicated-frame', the \
+`\\[universal-argument]' prefix also
requests that a new frame be made for the new buffer, so that the dedicated
frame is not changed."
(interactive "P")
@@ -7493,26 +7746,26 @@ frame is not changed."
(org-back-to-heading t)
(when (numberp arg)
(setq level (org-outline-level))
- (if (< arg 0) (setq arg (+ level arg)))
+ (when (< arg 0) (setq arg (+ level arg)))
(while (> (setq level (org-outline-level)) arg)
(org-up-heading-safe)))
(setq beg (point)
- heading (org-get-heading))
+ heading (org-get-heading 'no-tags))
(org-end-of-subtree t t)
- (if (org-at-heading-p) (backward-char 1))
+ (when (org-at-heading-p) (backward-char 1))
(setq end (point)))
- (if (and (buffer-live-p org-last-indirect-buffer)
- (not (eq org-indirect-buffer-display 'new-frame))
- (not arg))
- (kill-buffer org-last-indirect-buffer))
- (setq ibuf (org-get-indirect-buffer cbuf)
+ (when (and (buffer-live-p org-last-indirect-buffer)
+ (not (eq org-indirect-buffer-display 'new-frame))
+ (not arg))
+ (kill-buffer org-last-indirect-buffer))
+ (setq ibuf (org-get-indirect-buffer cbuf heading)
org-last-indirect-buffer ibuf)
(cond
((or (eq org-indirect-buffer-display 'new-frame)
(and arg (eq org-indirect-buffer-display 'dedicated-frame)))
(select-frame (make-frame))
(delete-other-windows)
- (org-pop-to-buffer-same-window ibuf)
+ (pop-to-buffer-same-window ibuf)
(org-set-frame-title heading))
((eq org-indirect-buffer-display 'dedicated-frame)
(raise-frame
@@ -7521,26 +7774,28 @@ frame is not changed."
org-indirect-dedicated-frame)
(setq org-indirect-dedicated-frame (make-frame)))))
(delete-other-windows)
- (org-pop-to-buffer-same-window ibuf)
+ (pop-to-buffer-same-window ibuf)
(org-set-frame-title (concat "Indirect: " heading)))
((eq org-indirect-buffer-display 'current-window)
- (org-pop-to-buffer-same-window ibuf))
+ (pop-to-buffer-same-window ibuf))
((eq org-indirect-buffer-display 'other-window)
(pop-to-buffer ibuf))
(t (error "Invalid value")))
- (if (featurep 'xemacs)
- (save-excursion (org-mode) (turn-on-font-lock)))
(narrow-to-region beg end)
- (show-all)
+ (outline-show-all)
(goto-char pos)
(run-hook-with-args 'org-cycle-hook 'all)
(and (window-live-p cwin) (select-window cwin))))
-(defun org-get-indirect-buffer (&optional buffer)
+(defun org-get-indirect-buffer (&optional buffer heading)
(setq buffer (or buffer (current-buffer)))
(let ((n 1) (base (buffer-name buffer)) bname)
(while (buffer-live-p
- (get-buffer (setq bname (concat base "-" (number-to-string n)))))
+ (get-buffer
+ (setq bname
+ (concat base "-"
+ (if heading (concat heading "-" (number-to-string n))
+ (number-to-string n))))))
(setq n (1+ n)))
(condition-case nil
(make-indirect-buffer buffer bname 'clone)
@@ -7548,57 +7803,70 @@ frame is not changed."
(defun org-set-frame-title (title)
"Set the title of the current frame to the string TITLE."
- ;; FIXME: how to name a single frame in XEmacs???
- (unless (featurep 'xemacs)
- (modify-frame-parameters (selected-frame) (list (cons 'name title)))))
+ (modify-frame-parameters (selected-frame) (list (cons 'name title))))
;;;; Structure editing
;;; Inserting headlines
-(defun org-previous-line-empty-p (&optional next)
- "Is the previous line a blank line?
-When NEXT is non-nil, check the next line instead."
+(defun org--line-empty-p (n)
+ "Is the Nth next line empty?
+
+Counts the current line as N = 1 and the previous line as N = 0;
+see `beginning-of-line'."
(save-excursion
(and (not (bobp))
- (or (beginning-of-line (if next 2 0)) t)
+ (or (beginning-of-line n) t)
(save-match-data
(looking-at "[ \t]*$")))))
-(defun org-insert-heading (&optional arg invisible-ok)
- "Insert a new heading or an item with the same depth at point.
-
-If point is at the beginning of a heading or a list item, insert
-a new heading or a new item above the current one. If point is
-at the beginning of a normal line, turn the line into a heading.
+(defun org-previous-line-empty-p ()
+ "Is the previous line a blank line?
+When NEXT is non-nil, check the next line instead."
+ (org--line-empty-p 0))
-If point is in the middle of a headline or a list item, split the
-headline or the item and create a new headline/item with the text
-in the current line after point \(see `org-M-RET-may-split-line'
-on how to modify this behavior).
+(defun org-next-line-empty-p ()
+ "Is the previous line a blank line?
+When NEXT is non-nil, check the next line instead."
+ (org--line-empty-p 2))
-With one universal prefix argument, set the user option
-`org-insert-heading-respect-content' to t for the duration of
-the command. This modifies the behavior described above in this
-ways: on list items and at the beginning of normal lines, force
-the insertion of a heading after the current subtree.
+(defun org-insert-heading (&optional arg invisible-ok top)
+ "Insert a new heading or an item with the same depth at point.
-With two universal prefix arguments, insert the heading at the
-end of the grandparent subtree. For example, if point is within
-a 2nd-level heading, then it will insert a 2nd-level heading at
-the end of the 1st-level parent heading.
+If point is at the beginning of a heading or a list item, insert
+a new heading or a new item above the current one. When at the
+beginning of a regular line of text, turn it into a heading.
+
+If point is in the middle of a line, split it and create a new
+headline/item with the text in the current line after point (see
+`org-M-RET-may-split-line' on how to modify this behavior). As
+a special case, on a headline, splitting can only happen on the
+title itself. E.g., this excludes breaking stars or tags.
+
+With a `\\[universal-argument]' prefix, set \
+`org-insert-heading-respect-content' to
+a non-nil value for the duration of the command. This forces the
+insertion of a heading after the current subtree, independently
+on the location of point.
+
+With a `\\[universal-argument] \\[universal-argument]' prefix, \
+insert the heading at the end of the tree
+above the current heading. For example, if point is within a
+2nd-level heading, then it will insert a 2nd-level heading at
+the end of the 1st-level parent subtree.
When INVISIBLE-OK is set, stop at invisible headlines when going
back. This is important for non-interactive uses of the
-command."
+command.
+
+When optional argument TOP is non-nil, insert a level 1 heading,
+unconditionally."
(interactive "P")
- (if (org-called-interactively-p 'any) (org-reveal))
- (let ((itemp (org-in-item-p))
+ (let ((itemp (and (not top) (org-in-item-p)))
(may-split (org-get-alist-option org-M-RET-may-split-line 'headline))
(respect-content (or org-insert-heading-respect-content
(equal arg '(4))))
- (initial-content "")
- (adjust-empty-lines t))
+ (initial-content ""))
(cond
@@ -7621,9 +7889,7 @@ command."
(insert "\n* ")))
(run-hooks 'org-insert-heading-hook))
- ((and itemp (not (member arg '((4) (16)))))
- ;; Insert an item
- (org-insert-item))
+ ((and itemp (not (member arg '((4) (16)))) (org-insert-item)))
(t
;; Maybe move at the end of the subtree
@@ -7639,25 +7905,26 @@ command."
(org-previous-line-empty-p)
;; We will decide later
nil))
- ;; Get a level string to fall back on
+ ;; Get a level string to fall back on.
(fix-level
(if (org-before-first-heading-p) "*"
(save-excursion
(org-back-to-heading t)
- (if (org-previous-line-empty-p) (setq empty-line-p t))
+ (when (org-previous-line-empty-p) (setq empty-line-p t))
(looking-at org-outline-regexp)
(make-string (1- (length (match-string 0))) ?*))))
(stars
(save-excursion
(condition-case nil
- (progn
+ (if top "* "
(org-back-to-heading invisible-ok)
(when (and (not on-heading)
(featurep 'org-inlinetask)
(integerp org-inlinetask-min-level)
(>= (length (match-string 0))
org-inlinetask-min-level))
- ;; Find a heading level before the inline task
+ ;; Find a heading level before the inline
+ ;; task.
(while (and (setq level (org-up-heading-safe))
(>= level org-inlinetask-min-level)))
(if (org-at-heading-p)
@@ -7668,23 +7935,22 @@ command."
(org-backward-heading-same-level
1 invisible-ok))
(= (point) (match-beginning 0)))
- (not (org-previous-line-empty-p t)))
+ (not (org-next-line-empty-p)))
(setq empty-line-p (or empty-line-p
(org-previous-line-empty-p))))
(match-string 0))
(error (or fix-level "* ")))))
(blank-a (cdr (assq 'heading org-blank-before-new-entry)))
- (blank (if (eq blank-a 'auto) empty-line-p blank-a))
- pos hide-previous previous-pos)
+ (blank (if (eq blank-a 'auto) empty-line-p blank-a)))
- ;; If we insert after content, move there and clean up whitespace
- (when (and respect-content
- (not (org-looking-at-p org-outline-regexp-bol)))
+ ;; If we insert after content, move there and clean up
+ ;; whitespace.
+ (when respect-content
(if (not (org-before-first-heading-p))
(org-end-of-subtree nil t)
(re-search-forward org-outline-regexp-bol)
(beginning-of-line 0))
- (skip-chars-backward " \r\n")
+ (skip-chars-backward " \r\t\n")
(and (not (looking-back "^\\*+" (line-beginning-position)))
(looking-at "[ \t]+") (replace-match ""))
(unless (eobp) (forward-char 1))
@@ -7692,14 +7958,17 @@ command."
(unless (bobp) (backward-char 1))
(insert "\n")))
- ;; If we are splitting, grab the text that should be moved to the new headline
+ ;; If we are splitting, grab the text that should be moved
+ ;; to the new headline.
(when may-split
- (if (org-on-heading-p)
- ;; This is a heading, we split intelligently (keeping tags)
+ (if (org-at-heading-p)
+ ;; This is a heading: split intelligently (keeping
+ ;; tags).
(let ((pos (point)))
- (goto-char (point-at-bol))
- (unless (looking-at org-complex-heading-regexp)
- (error "This should not happen"))
+ (beginning-of-line)
+ (let ((case-fold-search nil))
+ (unless (looking-at org-complex-heading-regexp)
+ (error "This should not happen")))
(when (and (match-beginning 4)
(> pos (match-beginning 4))
(< pos (match-end 4)))
@@ -7708,31 +7977,35 @@ command."
(delete-region (point) (match-end 4))
(if (looking-at "[ \t]*$")
(replace-match "")
- (insert (make-string (length initial-content) ?\ )))
+ (insert (make-string (length initial-content) ?\s)))
(setq initial-content (org-trim initial-content)))
(goto-char pos))
- ;; a normal line
+ ;; A normal line.
(setq initial-content
- (org-trim (buffer-substring (point) (point-at-eol))))
- (delete-region (point) (point-at-eol))))
+ (org-trim
+ (delete-and-extract-region (point) (line-end-position))))))
- ;; If we are at the beginning of the line, insert before it. Else after
+ ;; If we are at the beginning of the line, insert before it.
+ ;; Otherwise, after it.
(cond
((and (bolp) (looking-at "[ \t]*$")))
- ((and (bolp) (not (looking-at "[ \t]*$")))
- (open-line 1))
- (t
- (goto-char (point-at-eol))
- (insert "\n")))
+ ((bolp) (save-excursion (insert "\n")))
+ (t (end-of-line)
+ (insert "\n")))
;; Insert the new heading
(insert stars)
(just-one-space)
(insert initial-content)
- (when adjust-empty-lines
- (if (or (not blank)
- (and blank (not (org-previous-line-empty-p))))
- (org-N-empty-lines-before-current (if blank 1 0))))
+ (unless (and blank (org-previous-line-empty-p))
+ (org-N-empty-lines-before-current (if blank 1 0)))
+ ;; Adjust visibility, which may be messed up if we removed
+ ;; blank lines while previous entry was hidden.
+ (let ((bol (line-beginning-position)))
+ (dolist (o (overlays-at (1- bol)))
+ (when (and (eq (overlay-get o 'invisible) 'outline)
+ (eq (overlay-end o) bol))
+ (move-overlay o (overlay-start o) (1- bol)))))
(run-hooks 'org-insert-heading-hook)))))))
(defun org-N-empty-lines-before-current (N)
@@ -7752,20 +8025,23 @@ When NO-TAGS is non-nil, don't include tags.
When NO-TODO is non-nil, don't include TODO keywords."
(save-excursion
(org-back-to-heading t)
- (cond
- ((and no-tags no-todo)
- (looking-at org-complex-heading-regexp)
- (match-string 4))
- (no-tags
- (looking-at (concat org-outline-regexp
- "\\(.*?\\)"
- "\\(?:[ \t]+:[[:alnum:]:_@#%]+:\\)?[ \t]*$"))
- (match-string 1))
- (no-todo
- (looking-at org-todo-line-regexp)
- (match-string 3))
- (t (looking-at org-heading-regexp)
- (match-string 2)))))
+ (let ((case-fold-search nil))
+ (cond
+ ((and no-tags no-todo)
+ (looking-at org-complex-heading-regexp)
+ ;; Return value has to be a string, but match group 4 is
+ ;; optional.
+ (or (match-string 4) ""))
+ (no-tags
+ (looking-at (concat org-outline-regexp
+ "\\(.*?\\)"
+ "\\(?:[ \t]+:[[:alnum:]:_@#%]+:\\)?[ \t]*$"))
+ (match-string 1))
+ (no-todo
+ (looking-at org-todo-line-regexp)
+ (match-string 3))
+ (t (looking-at org-heading-regexp)
+ (match-string 2))))))
(defvar orgstruct-mode) ; defined below
@@ -7780,24 +8056,24 @@ This is a list with the following elements:
- the tags string, or nil."
(save-excursion
(org-back-to-heading t)
- (if (let (case-fold-search)
- (looking-at
- (if orgstruct-mode
- org-heading-regexp
- org-complex-heading-regexp)))
- (if orgstruct-mode
- (list (length (match-string 1))
- (org-reduced-level (length (match-string 1)))
- nil
- nil
- (match-string 2)
- nil)
- (list (length (match-string 1))
- (org-reduced-level (length (match-string 1)))
- (org-match-string-no-properties 2)
- (and (match-end 3) (aref (match-string 3) 2))
- (org-match-string-no-properties 4)
- (org-match-string-no-properties 5))))))
+ (when (let (case-fold-search)
+ (looking-at
+ (if orgstruct-mode
+ org-heading-regexp
+ org-complex-heading-regexp)))
+ (if orgstruct-mode
+ (list (length (match-string 1))
+ (org-reduced-level (length (match-string 1)))
+ nil
+ nil
+ (match-string 2)
+ nil)
+ (list (length (match-string 1))
+ (org-reduced-level (length (match-string 1)))
+ (match-string-no-properties 2)
+ (and (match-end 3) (aref (match-string 3) 2))
+ (match-string-no-properties 4)
+ (match-string-no-properties 5))))))
(defun org-get-entry ()
"Get the entry text, after heading, entire subtree."
@@ -7805,6 +8081,24 @@ This is a list with the following elements:
(org-back-to-heading t)
(buffer-substring (point-at-bol 2) (org-end-of-subtree t))))
+(defun org-edit-headline (&optional heading)
+ "Edit the current headline.
+Set it to HEADING when provided."
+ (interactive)
+ (org-with-wide-buffer
+ (org-back-to-heading t)
+ (let ((case-fold-search nil))
+ (when (looking-at org-complex-heading-regexp)
+ (let* ((old (match-string-no-properties 4))
+ (new (save-match-data
+ (org-trim (or heading (read-string "Edit: " old))))))
+ (unless (equal old new)
+ (if old (replace-match new t t nil 4)
+ (goto-char (or (match-end 3) (match-end 2) (match-end 1)))
+ (insert " " new))
+ (org-set-tags nil t)
+ (when (looking-at "[ \t]*$") (replace-match ""))))))))
+
(defun org-insert-heading-after-current ()
"Insert a new heading with same level as current, after current subtree."
(interactive)
@@ -7825,9 +8119,14 @@ This is a list with the following elements:
(defun org-insert-todo-heading (arg &optional force-heading)
"Insert a new heading with the same level and TODO state as current heading.
-If the heading has no TODO state, or if the state is DONE, use the first
-state (TODO by default). Also with one prefix arg, force first state. With
-two prefix args, force inserting at the end of the parent subtree."
+
+If the heading has no TODO state, or if the state is DONE, use
+the first state (TODO by default). Also with one prefix arg,
+force first state. With two prefix args, force inserting at the
+end of the parent subtree.
+
+When called at a plain list item, insert a new item with an
+unchecked check box."
(interactive "P")
(when (or force-heading (not (org-insert-item 'checkbox)))
(org-insert-heading (or (and (equal arg '(16)) '(16))
@@ -7835,19 +8134,18 @@ two prefix args, force inserting at the end of the parent subtree."
(save-excursion
(org-back-to-heading)
(outline-previous-heading)
- (looking-at org-todo-line-regexp))
- (let*
- ((new-mark-x
- (if (or (equal arg '(4))
- (not (match-beginning 2))
- (member (match-string 2) org-done-keywords))
- (car org-todo-keywords-1)
- (match-string 2)))
- (new-mark
- (or
- (run-hook-with-args-until-success
- 'org-todo-get-default-hook new-mark-x nil)
- new-mark-x)))
+ (let ((case-fold-search nil)) (looking-at org-todo-line-regexp)))
+ (let* ((new-mark-x
+ (if (or (equal arg '(4))
+ (not (match-beginning 2))
+ (member (match-string 2) org-done-keywords))
+ (car org-todo-keywords-1)
+ (match-string 2)))
+ (new-mark
+ (or
+ (run-hook-with-args-until-success
+ 'org-todo-get-default-hook new-mark-x nil)
+ new-mark-x)))
(beginning-of-line 1)
(and (looking-at org-outline-regexp) (goto-char (match-end 0))
(if org-treat-insert-todo-heading-as-state-change
@@ -7895,18 +8193,17 @@ See also `org-promote'."
(org-fix-position-after-promote))
(defun org-demote-subtree ()
- "Demote the entire subtree. See `org-demote'.
-See also `org-promote'."
+ "Demote the entire subtree.
+See `org-demote' and `org-promote'."
(interactive)
(save-excursion
(org-with-limited-levels (org-map-tree 'org-demote)))
(org-fix-position-after-promote))
-
(defun org-do-promote ()
"Promote the current heading higher up the tree.
-If the region is active in `transient-mark-mode', promote all headings
-in the region."
+If the region is active in `transient-mark-mode', promote all
+headings in the region."
(interactive)
(save-excursion
(if (org-region-active-p)
@@ -7916,8 +8213,8 @@ in the region."
(defun org-do-demote ()
"Demote the current heading lower down the tree.
-If the region is active in `transient-mark-mode', demote all headings
-in the region."
+If the region is active in `transient-mark-mode', demote all
+headings in the region."
(interactive)
(save-excursion
(if (org-region-active-p)
@@ -7926,23 +8223,24 @@ in the region."
(org-fix-position-after-promote))
(defun org-fix-position-after-promote ()
- "Make sure that after pro/demotion cursor position is right."
+ "Fix cursor position and indentation after demoting/promoting."
(let ((pos (point)))
(when (save-excursion
- (beginning-of-line 1)
- (looking-at org-todo-line-regexp)
- (or (equal pos (match-end 1)) (equal pos (match-end 2))))
+ (beginning-of-line)
+ (let ((case-fold-search nil)) (looking-at org-todo-line-regexp))
+ (or (eq pos (match-end 1)) (eq pos (match-end 2))))
(cond ((eobp) (insert " "))
((eolp) (insert " "))
- ((equal (char-after) ?\ ) (forward-char 1))))))
+ ((equal (char-after) ?\s) (forward-char 1))))))
(defun org-current-level ()
"Return the level of the current entry, or nil if before the first headline.
-The level is the number of stars at the beginning of the headline."
- (save-excursion
- (org-with-limited-levels
- (if (ignore-errors (org-back-to-heading t))
- (funcall outline-level)))))
+The level is the number of stars at the beginning of the
+headline. Use `org-reduced-level' to remove the effect of
+`org-odd-levels'. Unlike to `org-outline-level', this function
+ignores inlinetasks."
+ (let ((level (org-with-limited-levels (org-outline-level))))
+ (and (> level 0) level)))
(defun org-get-previous-line-level ()
"Return the outline depth of the last headline before the current line.
@@ -7978,50 +8276,39 @@ even level numbers will become the next higher odd number."
((< change 0) (max 1 (1+ (* 2 (/ (+ level (* 2 change)) 2))))))
(max 1 (+ level (or change 0)))))
-(if (boundp 'define-obsolete-function-alias)
- (if (or (featurep 'xemacs) (< emacs-major-version 23))
- (define-obsolete-function-alias 'org-get-legal-level
- 'org-get-valid-level)
- (define-obsolete-function-alias 'org-get-legal-level
- 'org-get-valid-level "23.1")))
-
(defun org-promote ()
- "Promote the current heading higher up the tree.
-If the region is active in `transient-mark-mode', promote all headings
-in the region."
- (org-back-to-heading t)
- (let* ((level (save-match-data (funcall outline-level)))
- (after-change-functions (remove 'flyspell-after-change-function
- after-change-functions))
- (up-head (concat (make-string (org-get-valid-level level -1) ?*) " "))
- (diff (abs (- level (length up-head) -1))))
- (cond ((and (= level 1) org-called-with-limited-levels
- org-allow-promoting-top-level-subtree)
- (replace-match "# " nil t))
- ((= level 1)
- (user-error "Cannot promote to level 0. UNDO to recover if necessary"))
- (t (replace-match up-head nil t)))
- ;; Fixup tag positioning
- (unless (= level 1)
- (and org-auto-align-tags (org-set-tags nil 'ignore-column))
- (if org-adapt-indentation (org-fixup-indentation (- diff))))
- (run-hooks 'org-after-promote-entry-hook)))
+ "Promote the current heading higher up the tree."
+ (org-with-wide-buffer
+ (org-back-to-heading t)
+ (let* ((after-change-functions (remq 'flyspell-after-change-function
+ after-change-functions))
+ (level (save-match-data (funcall outline-level)))
+ (up-head (concat (make-string (org-get-valid-level level -1) ?*) " "))
+ (diff (abs (- level (length up-head) -1))))
+ (cond
+ ((and (= level 1) org-allow-promoting-top-level-subtree)
+ (replace-match "# " nil t))
+ ((= level 1)
+ (user-error "Cannot promote to level 0. UNDO to recover if necessary"))
+ (t (replace-match up-head nil t)))
+ (unless (= level 1)
+ (when org-auto-align-tags (org-set-tags nil 'ignore-column))
+ (when org-adapt-indentation (org-fixup-indentation (- diff))))
+ (run-hooks 'org-after-promote-entry-hook))))
(defun org-demote ()
- "Demote the current heading lower down the tree.
-If the region is active in `transient-mark-mode', demote all headings
-in the region."
- (org-back-to-heading t)
- (let* ((level (save-match-data (funcall outline-level)))
- (after-change-functions (remove 'flyspell-after-change-function
- after-change-functions))
- (down-head (concat (make-string (org-get-valid-level level 1) ?*) " "))
- (diff (abs (- level (length down-head) -1))))
- (replace-match down-head nil t)
- ;; Fixup tag positioning
- (and org-auto-align-tags (org-set-tags nil 'ignore-column))
- (if org-adapt-indentation (org-fixup-indentation diff))
- (run-hooks 'org-after-demote-entry-hook)))
+ "Demote the current heading lower down the tree."
+ (org-with-wide-buffer
+ (org-back-to-heading t)
+ (let* ((after-change-functions (remq 'flyspell-after-change-function
+ after-change-functions))
+ (level (save-match-data (funcall outline-level)))
+ (down-head (concat (make-string (org-get-valid-level level 1) ?*) " "))
+ (diff (abs (- level (length down-head) -1))))
+ (replace-match down-head nil t)
+ (when org-auto-align-tags (org-set-tags nil 'ignore-column))
+ (when org-adapt-indentation (org-fixup-indentation diff))
+ (run-hooks 'org-after-demote-entry-hook))))
(defun org-cycle-level ()
"Cycle the level of an empty headline through possible states.
@@ -8036,32 +8323,32 @@ After top level, it switches back to sibling level."
(cond
;; If first headline in file, promote to top-level.
((= prev-level 0)
- (loop repeat (/ (- cur-level 1) (org-level-increment))
- do (org-do-promote)))
+ (cl-loop repeat (/ (- cur-level 1) (org-level-increment))
+ do (org-do-promote)))
;; If same level as prev, demote one.
((= prev-level cur-level)
(org-do-demote))
;; If parent is top-level, promote to top level if not already.
((= prev-level 1)
- (loop repeat (/ (- cur-level 1) (org-level-increment))
- do (org-do-promote)))
+ (cl-loop repeat (/ (- cur-level 1) (org-level-increment))
+ do (org-do-promote)))
;; If top-level, return to prev-level.
((= cur-level 1)
- (loop repeat (/ (- prev-level 1) (org-level-increment))
- do (org-do-demote)))
+ (cl-loop repeat (/ (- prev-level 1) (org-level-increment))
+ do (org-do-demote)))
;; If less than prev-level, promote one.
((< cur-level prev-level)
(org-do-promote))
;; If deeper than prev-level, promote until higher than
;; prev-level.
((> cur-level prev-level)
- (loop repeat (+ 1 (/ (- cur-level prev-level) (org-level-increment)))
- do (org-do-promote))))
+ (cl-loop repeat (+ 1 (/ (- cur-level prev-level) (org-level-increment)))
+ do (org-do-promote))))
t))))
(defun org-map-tree (fun)
"Call FUN for every heading underneath the current one."
- (org-back-to-heading)
+ (org-back-to-heading t)
(let ((level (funcall outline-level)))
(save-excursion
(funcall fun)
@@ -8077,39 +8364,123 @@ After top level, it switches back to sibling level."
(save-excursion
(setq end (copy-marker end))
(goto-char beg)
- (if (and (re-search-forward org-outline-regexp-bol nil t)
- (< (point) end))
- (funcall fun))
+ (when (and (re-search-forward org-outline-regexp-bol nil t)
+ (< (point) end))
+ (funcall fun))
(while (and (progn
(outline-next-heading)
(< (point) end))
(not (eobp)))
(funcall fun)))))
-(defvar org-property-end-re) ; silence byte-compiler
(defun org-fixup-indentation (diff)
"Change the indentation in the current entry by DIFF.
-However, if any line in the current entry has no indentation, or if it
-would end up with no indentation after the change, nothing at all is done."
- (save-excursion
- (let ((end (save-excursion (outline-next-heading)
- (point-marker)))
- (prohibit (if (> diff 0)
- "^\\S-"
- (concat "^ \\{0," (int-to-string (- diff)) "\\}\\S-")))
- col)
- (unless (save-excursion (end-of-line 1)
- (re-search-forward prohibit end t))
- (while (and (< (point) end)
- (re-search-forward "^[ \t]+" end t))
- (goto-char (match-end 0))
- (setq col (current-column))
- (if (< diff 0) (replace-match ""))
- (org-indent-to-column (+ diff col))))
- (move-marker end nil))))
+
+DIFF is an integer. Indentation is done according to the
+following rules:
+
+ - Planning information and property drawers are always indented
+ according to the new level of the headline;
+
+ - Footnote definitions and their contents are ignored;
+
+ - Inlinetasks' boundaries are not shifted;
+
+ - Empty lines are ignored;
+
+ - Other lines' indentation are shifted by DIFF columns, unless
+ it would introduce a structural change in the document, in
+ which case no shifting is done at all.
+
+Assume point is at a heading or an inlinetask beginning."
+ (org-with-wide-buffer
+ (narrow-to-region (line-beginning-position)
+ (save-excursion
+ (if (org-with-limited-levels (org-at-heading-p))
+ (org-with-limited-levels (outline-next-heading))
+ (org-inlinetask-goto-end))
+ (point)))
+ (forward-line)
+ ;; Indent properly planning info and property drawer.
+ (when (looking-at-p org-planning-line-re)
+ (org-indent-line)
+ (forward-line))
+ (when (looking-at org-property-drawer-re)
+ (goto-char (match-end 0))
+ (forward-line)
+ (save-excursion (org-indent-region (match-beginning 0) (match-end 0))))
+ (catch 'no-shift
+ (when (zerop diff) (throw 'no-shift nil))
+ ;; If DIFF is negative, first check if a shift is possible at all
+ ;; (e.g., it doesn't break structure). This can only happen if
+ ;; some contents are not properly indented.
+ (let ((case-fold-search t))
+ (when (< diff 0)
+ (let ((diff (- diff))
+ (forbidden-re (concat org-outline-regexp
+ "\\|"
+ (substring org-footnote-definition-re 1))))
+ (save-excursion
+ (while (not (eobp))
+ (cond
+ ((looking-at-p "[ \t]*$") (forward-line))
+ ((and (looking-at-p org-footnote-definition-re)
+ (let ((e (org-element-at-point)))
+ (and (eq (org-element-type e) 'footnote-definition)
+ (goto-char (org-element-property :end e))))))
+ ((looking-at-p org-outline-regexp) (forward-line))
+ ;; Give up if shifting would move before column 0 or
+ ;; if it would introduce a headline or a footnote
+ ;; definition.
+ (t
+ (skip-chars-forward " \t")
+ (let ((ind (current-column)))
+ (when (or (< ind diff)
+ (and (= ind diff) (looking-at-p forbidden-re)))
+ (throw 'no-shift nil)))
+ ;; Ignore contents of example blocks and source
+ ;; blocks if their indentation is meant to be
+ ;; preserved. Jump to block's closing line.
+ (beginning-of-line)
+ (or (and (looking-at-p "[ \t]*#\\+BEGIN_\\(EXAMPLE\\|SRC\\)")
+ (let ((e (org-element-at-point)))
+ (and (memq (org-element-type e)
+ '(example-block src-block))
+ (or org-src-preserve-indentation
+ (org-element-property :preserve-indent e))
+ (goto-char (org-element-property :end e))
+ (progn (skip-chars-backward " \r\t\n")
+ (beginning-of-line)
+ t))))
+ (forward-line))))))))
+ ;; Shift lines but footnote definitions, inlinetasks boundaries
+ ;; by DIFF. Also skip contents of source or example blocks
+ ;; when indentation is meant to be preserved.
+ (while (not (eobp))
+ (cond
+ ((and (looking-at-p org-footnote-definition-re)
+ (let ((e (org-element-at-point)))
+ (and (eq (org-element-type e) 'footnote-definition)
+ (goto-char (org-element-property :end e))))))
+ ((looking-at-p org-outline-regexp) (forward-line))
+ ((looking-at-p "[ \t]*$") (forward-line))
+ (t
+ (indent-line-to (+ (org-get-indentation) diff))
+ (beginning-of-line)
+ (or (and (looking-at-p "[ \t]*#\\+BEGIN_\\(EXAMPLE\\|SRC\\)")
+ (let ((e (org-element-at-point)))
+ (and (memq (org-element-type e)
+ '(example-block src-block))
+ (or org-src-preserve-indentation
+ (org-element-property :preserve-indent e))
+ (goto-char (org-element-property :end e))
+ (progn (skip-chars-backward " \r\t\n")
+ (beginning-of-line)
+ t))))
+ (forward-line)))))))))
(defun org-convert-to-odd-levels ()
- "Convert an org-mode file with all levels allowed to one with odd levels.
+ "Convert an Org file with all levels allowed to one with odd levels.
This will leave level 1 alone, convert level 2 to level 3, level 3 to
level 5 etc."
(interactive)
@@ -8125,7 +8496,7 @@ level 5 etc."
(end-of-line 1))))))
(defun org-convert-to-oddeven-levels ()
- "Convert an org-mode file with only odd levels to one with odd/even levels.
+ "Convert an Org file with only odd levels to one with odd/even levels.
This promotes level 3 to level 2, level 5 to level 3 etc. If the
file contains a section with an even level, conversion would
destroy the structure of the file. An error is signaled in this
@@ -8134,7 +8505,7 @@ case."
(goto-char (point-min))
;; First check if there are no even levels
(when (re-search-forward "^\\(\\*\\*\\)+ " nil t)
- (org-show-context t)
+ (org-show-set-visibility 'canonical)
(error "Not all levels are odd in this file. Conversion not possible"))
(when (yes-or-no-p "Are you sure you want to globally change levels to odd-even? ")
(let ((outline-regexp org-outline-regexp)
@@ -8177,7 +8548,7 @@ case."
(setq beg (point)))
(save-match-data
(save-excursion (outline-end-of-heading)
- (setq folded (outline-invisible-p)))
+ (setq folded (org-invisible-p)))
(progn (org-end-of-subtree nil t)
(unless (eobp) (backward-char))))
(outline-next-heading)
@@ -8196,12 +8567,12 @@ case."
(progn (goto-char beg0)
(user-error "Cannot move past superior level or buffer limit")))
(setq cnt (1- cnt)))
- (if (> arg 0)
- ;; Moving forward - still need to move over subtree
- (progn (org-end-of-subtree t t)
- (save-excursion
- (org-back-over-empty-lines)
- (or (bolp) (newline)))))
+ (when (> arg 0)
+ ;; Moving forward - still need to move over subtree
+ (org-end-of-subtree t t)
+ (save-excursion
+ (org-back-over-empty-lines)
+ (or (bolp) (newline))))
(setq ne-ins (org-back-over-empty-lines))
(move-marker ins-point (point))
(setq txt (buffer-substring beg end))
@@ -8230,9 +8601,9 @@ case."
(insert (make-string (- ne-ins ne-beg) ?\n)))
(move-marker ins-point nil)
(if folded
- (hide-subtree)
+ (outline-hide-subtree)
(org-show-entry)
- (show-children)
+ (org-show-children)
(org-cycle-hide-drawers 'children))
(org-clean-visibility-after-subtree-move)
;; move back to the initial column we were at
@@ -8264,7 +8635,7 @@ of some markers in the region, even if CUT is non-nil. This is
useful if the caller implements cut-and-paste as copy-then-paste-then-cut."
(interactive "p")
(let (beg end folded (beg0 (point)))
- (if (org-called-interactively-p 'any)
+ (if (called-interactively-p 'any)
(org-back-to-heading nil) ; take what looks like a subtree
(org-back-to-heading t)) ; take what is really there
(setq beg (point))
@@ -8273,11 +8644,14 @@ useful if the caller implements cut-and-paste as copy-then-paste-then-cut."
(if nosubtrees
(outline-next-heading)
(save-excursion (outline-end-of-heading)
- (setq folded (outline-invisible-p)))
- (condition-case nil
- (org-forward-heading-same-level (1- n) t)
- (error nil))
+ (setq folded (org-invisible-p)))
+ (ignore-errors (org-forward-heading-same-level (1- n) t))
(org-end-of-subtree t t)))
+ ;; Include the end of an inlinetask
+ (when (and (featurep 'org-inlinetask)
+ (looking-at-p (concat (org-inlinetask-outline-regexp)
+ "END[ \t]*$")))
+ (end-of-line))
(setq end (point))
(goto-char beg0)
(when (> end beg)
@@ -8290,7 +8664,7 @@ useful if the caller implements cut-and-paste as copy-then-paste-then-cut."
(if cut "Cut" "Copied")
(length org-subtree-clip)))))
-(defun org-paste-subtree (&optional level tree for-yank)
+(defun org-paste-subtree (&optional level tree for-yank remove)
"Paste the clipboard as a subtree, with modification of headline level.
The entire subtree is promoted or demoted in order to match a new headline
level.
@@ -8313,15 +8687,17 @@ If optional TREE is given, use this text instead of the kill ring.
When FOR-YANK is set, this is called by `org-yank'. In this case, do not
move back over whitespace before inserting, and move point to the end of
-the inserted text when done."
+the inserted text when done.
+
+When REMOVE is non-nil, remove the subtree from the clipboard."
(interactive "P")
(setq tree (or tree (and kill-ring (current-kill 0))))
(unless (org-kill-is-subtree-p tree)
(user-error "%s"
- (substitute-command-keys
- "The kill is not a (set of) tree(s) - please use \\[yank] to yank anyway")))
+ (substitute-command-keys
+ "The kill is not a (set of) tree(s) - please use \\[yank] to yank anyway")))
(org-with-limited-levels
- (let* ((visp (not (outline-invisible-p)))
+ (let* ((visp (not (org-invisible-p)))
(txt tree)
(^re_ "\\(\\*+\\)[ \t]*")
(old-level (if (string-match org-outline-regexp-bol txt)
@@ -8364,22 +8740,22 @@ the inserted text when done."
(org-odd-levels-only nil)
beg end newend)
;; Remove the forced level indicator
- (if force-level
- (delete-region (point-at-bol) (point)))
+ (when force-level
+ (delete-region (point-at-bol) (point)))
;; Paste
(beginning-of-line (if (bolp) 1 2))
(setq beg (point))
(and (fboundp 'org-id-paste-tracker) (org-id-paste-tracker txt))
(insert-before-markers txt)
- (unless (string-match "\n\\'" txt) (insert "\n"))
+ (unless (string-suffix-p "\n" txt) (insert "\n"))
(setq newend (point))
(org-reinstall-markers-in-region beg)
(setq end (point))
(goto-char beg)
(skip-chars-forward " \t\n\r")
(setq beg (point))
- (if (and (outline-invisible-p) visp)
- (save-excursion (outline-show-heading)))
+ (when (and (org-invisible-p) visp)
+ (save-excursion (outline-show-heading)))
;; Shift if necessary
(unless (= shift 0)
(save-restriction
@@ -8389,15 +8765,16 @@ the inserted text when done."
(setq shift (+ delta shift)))
(goto-char (point-min))
(setq newend (point-max))))
- (when (or (org-called-interactively-p 'interactive) for-yank)
+ (when (or (called-interactively-p 'interactive) for-yank)
(message "Clipboard pasted as level %d subtree" new-level))
- (if (and (not for-yank) ; in this case, org-yank will decide about folding
- kill-ring
- (eq org-subtree-clip (current-kill 0))
- org-subtree-clip-folded)
- ;; The tree was folded before it was killed/copied
- (hide-subtree))
- (and for-yank (goto-char newend)))))
+ (when (and (not for-yank) ; in this case, org-yank will decide about folding
+ kill-ring
+ (eq org-subtree-clip (current-kill 0))
+ org-subtree-clip-folded)
+ ;; The tree was folded before it was killed/copied
+ (outline-hide-subtree))
+ (and for-yank (goto-char newend))
+ (and remove (setq kill-ring (cdr kill-ring))))))
(defun org-kill-is-subtree-p (&optional txt)
"Check if the current kill is an outline subtree, or a set of trees.
@@ -8447,15 +8824,14 @@ called immediately, to move the markers with the entries."
"Check if MARKER is between BEG and END.
If yes, remember the marker and the distance to BEG."
(when (and (marker-buffer marker)
- (equal (marker-buffer marker) (current-buffer)))
- (if (and (>= marker beg) (< marker end))
- (push (cons marker (- marker beg)) org-markers-to-move))))
+ (equal (marker-buffer marker) (current-buffer))
+ (>= marker beg) (< marker end))
+ (push (cons marker (- marker beg)) org-markers-to-move)))
(defun org-reinstall-markers-in-region (beg)
"Move all remembered markers to their position relative to BEG."
- (mapc (lambda (x)
- (move-marker (car x) (+ beg (cdr x))))
- org-markers-to-move)
+ (dolist (x org-markers-to-move)
+ (move-marker (car x) (+ beg (cdr x))))
(setq org-markers-to-move nil))
(defun org-narrow-to-subtree ()
@@ -8467,7 +8843,7 @@ If yes, remember the marker and the distance to BEG."
(narrow-to-region
(progn (org-back-to-heading t) (point))
(progn (org-end-of-subtree t t)
- (if (and (org-at-heading-p) (not (eobp))) (backward-char 1))
+ (when (and (org-at-heading-p) (not (eobp))) (backward-char 1))
(point)))))))
(defun org-narrow-to-block ()
@@ -8480,10 +8856,6 @@ If yes, remember the marker and the distance to BEG."
(narrow-to-region (car blockp) (cdr blockp))
(user-error "Not in a block"))))
-(eval-when-compile
- (defvar org-property-drawer-re))
-
-(defvar org-property-start-re) ;; defined below
(defun org-clone-subtree-with-time-shift (n &optional shift)
"Clone the task (subtree) at point N times.
The clones will be inserted as siblings.
@@ -8500,6 +8872,9 @@ stamps in the subtree shifted for each clone produced. If SHIFT
is nil or the empty string, time stamps will be left alone. The
ID property of the original subtree is removed.
+In each clone, all the CLOCK entries will be removed. This
+prevents Org from considering that the clocked times overlap.
+
If the original subtree did contain time stamps with a repeater,
the following will happen:
- the repeater will be removed in each clone
@@ -8510,80 +8885,86 @@ the following will happen:
- the start days in the repeater in the original entry will be shifted
to past the last clone.
In this way you can spell out a number of instances of a repeating task,
-and still retain the repeater to cover future instances of the task."
+and still retain the repeater to cover future instances of the task.
+
+As described above, N+1 clones are produced when the original
+subtree has a repeater. Setting N to 0, then, can be used to
+remove the repeater from a subtree and create a shifted clone
+with the original repeater."
(interactive "nNumber of clones to produce: ")
- (let ((shift
- (or shift
- (if (and (not (equal current-prefix-arg '(4)))
- (save-excursion
- (re-search-forward org-ts-regexp-both
- (save-excursion
- (org-end-of-subtree t)
- (point)) t)))
- (read-from-minibuffer
- "Date shift per clone (e.g. +1w, empty to copy unchanged): ")
- ""))) ;; No time shift
- (n-no-remove -1)
- (drawer-re org-drawer-regexp)
- beg end template task idprop
- shift-n shift-what doshift nmin nmax)
- (if (not (and (integerp n) (> n 0)))
- (error "Invalid number of replications %s" n))
- (if (and (setq doshift (and (stringp shift) (string-match "\\S-" shift)))
- (not (string-match "\\`[ \t]*\\+?\\([0-9]+\\)\\([hdwmy]\\)[ \t]*\\'"
- shift)))
- (error "Invalid shift specification %s" shift))
- (when doshift
- (setq shift-n (string-to-number (match-string 1 shift))
- shift-what (cdr (assoc (match-string 2 shift)
- '(("d" . day) ("w" . week)
- ("m" . month) ("y" . year))))))
- (if (eq shift-what 'week) (setq shift-n (* 7 shift-n) shift-what 'day))
- (setq nmin 1 nmax n)
- (org-back-to-heading t)
- (setq beg (point))
- (setq idprop (org-entry-get nil "ID"))
- (org-end-of-subtree t t)
- (or (bolp) (insert "\n"))
- (setq end (point))
- (setq template (buffer-substring beg end))
- (when (and doshift
- (string-match "<[^<>\n]+ [.+]?\\+[0-9]+[hdwmy][^<>\n]*>" template))
- (delete-region beg end)
- (setq end beg)
- (setq nmin 0 nmax (1+ nmax) n-no-remove nmax))
- (goto-char end)
- (loop for n from nmin to nmax do
- ;; prepare clone
- (with-temp-buffer
- (insert template)
- (org-mode)
- (goto-char (point-min))
- (org-show-subtree)
- (and idprop (if org-clone-delete-id
- (org-entry-delete nil "ID")
- (org-id-get-create t)))
- (unless (= n 0)
- (while (re-search-forward "^[ \t]*CLOCK:.*$" nil t)
- (kill-whole-line))
- (goto-char (point-min))
- (while (re-search-forward drawer-re nil t)
- (mapc (lambda (d)
- (org-remove-empty-drawer-at d (point)))
- org-drawers)))
- (goto-char (point-min))
- (when doshift
- (while (re-search-forward org-ts-regexp-both nil t)
- (org-timestamp-change (* n shift-n) shift-what))
- (unless (= n n-no-remove)
- (goto-char (point-min))
- (while (re-search-forward org-ts-regexp nil t)
- (save-excursion
- (goto-char (match-beginning 0))
- (if (looking-at "<[^<>\n]+\\( +[.+]?\\+[0-9]+[hdwmy]\\)")
- (delete-region (match-beginning 1) (match-end 1)))))))
- (setq task (buffer-string)))
- (insert task))
+ (unless (wholenump n) (user-error "Invalid number of replications %s" n))
+ (when (org-before-first-heading-p) (user-error "No subtree to clone"))
+ (let* ((beg (save-excursion (org-back-to-heading t) (point)))
+ (end-of-tree (save-excursion (org-end-of-subtree t t) (point)))
+ (shift
+ (or shift
+ (if (and (not (equal current-prefix-arg '(4)))
+ (save-excursion
+ (goto-char beg)
+ (re-search-forward org-ts-regexp-both end-of-tree t)))
+ (read-from-minibuffer
+ "Date shift per clone (e.g. +1w, empty to copy unchanged): ")
+ ""))) ;No time shift
+ (doshift
+ (and (org-string-nw-p shift)
+ (or (string-match "\\`[ \t]*\\+?\\([0-9]+\\)\\([dwmy]\\)[ \t]*\\'"
+ shift)
+ (user-error "Invalid shift specification %s" shift)))))
+ (goto-char end-of-tree)
+ (unless (bolp) (insert "\n"))
+ (let* ((end (point))
+ (template (buffer-substring beg end))
+ (shift-n (and doshift (string-to-number (match-string 1 shift))))
+ (shift-what (pcase (and doshift (match-string 2 shift))
+ (`nil nil)
+ ("d" 'day)
+ ("w" (setq shift-n (* 7 shift-n)) 'day)
+ ("m" 'month)
+ ("y" 'year)
+ (_ (error "Unsupported time unit"))))
+ (nmin 1)
+ (nmax n)
+ (n-no-remove -1)
+ (idprop (org-entry-get nil "ID")))
+ (when (and doshift
+ (string-match-p "<[^<>\n]+ [.+]?\\+[0-9]+[hdwmy][^<>\n]*>"
+ template))
+ (delete-region beg end)
+ (setq end beg)
+ (setq nmin 0)
+ (setq nmax (1+ nmax))
+ (setq n-no-remove nmax))
+ (goto-char end)
+ (cl-loop for n from nmin to nmax do
+ (insert
+ ;; Prepare clone.
+ (with-temp-buffer
+ (insert template)
+ (org-mode)
+ (goto-char (point-min))
+ (org-show-subtree)
+ (and idprop (if org-clone-delete-id
+ (org-entry-delete nil "ID")
+ (org-id-get-create t)))
+ (unless (= n 0)
+ (while (re-search-forward org-clock-line-re nil t)
+ (delete-region (line-beginning-position)
+ (line-beginning-position 2)))
+ (goto-char (point-min))
+ (while (re-search-forward org-drawer-regexp nil t)
+ (org-remove-empty-drawer-at (point))))
+ (goto-char (point-min))
+ (when doshift
+ (while (re-search-forward org-ts-regexp-both nil t)
+ (org-timestamp-change (* n shift-n) shift-what))
+ (unless (= n n-no-remove)
+ (goto-char (point-min))
+ (while (re-search-forward org-ts-regexp nil t)
+ (save-excursion
+ (goto-char (match-beginning 0))
+ (when (looking-at "<[^<>\n]+\\( +[.+]?\\+[0-9]+[hdwmy]\\)")
+ (delete-region (match-beginning 1) (match-end 1)))))))
+ (buffer-string)))))
(goto-char beg)))
;;; Outline Sorting
@@ -8621,7 +9002,8 @@ hook gets called. When a region or a plain list is sorted, the cursor
will be in the first entry of the sorted region/list.")
(defun org-sort-entries
- (&optional with-case sorting-type getkey-func compare-func property)
+ (&optional with-case sorting-type getkey-func compare-func property
+ interactive?)
"Sort entries on a certain level of an outline tree.
If there is an active region, the entries in the region are sorted.
Else, if the cursor is before the first entry, sort the top-level items.
@@ -8632,33 +9014,40 @@ a time stamp, by a property, by priority order, or by a custom function.
The command prompts for the sorting type unless it has been given to the
function through the SORTING-TYPE argument, which needs to be a character,
-\(?n ?N ?a ?A ?t ?T ?s ?S ?d ?D ?p ?P ?o ?O ?r ?R ?f ?F). Here is the
-precise meaning of each character:
+\(?n ?N ?a ?A ?t ?T ?s ?S ?d ?D ?p ?P ?o ?O ?r ?R ?f ?F ?k ?K). Here is
+the precise meaning of each character:
-n Numerically, by converting the beginning of the entry/item to a number.
a Alphabetically, ignoring the TODO keyword and the priority, if any.
-o By order of TODO keywords.
-t By date/time, either the first active time stamp in the entry, or, if
- none exist, by the first inactive one.
-s By the scheduled date/time.
-d By deadline date/time.
c By creation time, which is assumed to be the first inactive time stamp
at the beginning of a line.
+d By deadline date/time.
+k By clocking time.
+n Numerically, by converting the beginning of the entry/item to a number.
+o By order of TODO keywords.
p By priority according to the cookie.
r By the value of a property.
+s By scheduled date/time.
+t By date/time, either the first active time stamp in the entry, or, if
+ none exist, by the first inactive one.
Capital letters will reverse the sort order.
If the SORTING-TYPE is ?f or ?F, then GETKEY-FUNC specifies a function to be
-called with point at the beginning of the record. It must return either
-a string or a number that should serve as the sorting key for that record.
+called with point at the beginning of the record. It must return a
+value that is compatible with COMPARE-FUNC, the function used to
+compare entries.
Comparing entries ignores case by default. However, with an optional argument
WITH-CASE, the sorting considers case as well.
Sorting is done against the visible part of the headlines, it ignores hidden
-links."
- (interactive "P")
+links.
+
+When sorting is done, call `org-after-sorting-entries-or-items-hook'.
+
+A non-nil value for INTERACTIVE? is used to signal that this
+function is being called interactively."
+ (interactive (list current-prefix-arg nil nil nil nil t))
(let ((case-func (if with-case 'identity 'downcase))
(cmstr
;; The clock marker is lost when using `sort-subr', let's
@@ -8677,10 +9066,10 @@ links."
(setq end (region-end)
what "region")
(goto-char (region-beginning))
- (if (not (org-at-heading-p)) (outline-next-heading))
+ (unless (org-at-heading-p) (outline-next-heading))
(setq start (point)))
((or (org-at-heading-p)
- (condition-case nil (progn (org-back-to-heading) t) (error nil)))
+ (ignore-errors (progn (org-back-to-heading) t)))
;; we will sort the children of the current headline
(org-back-to-heading)
(setq start (point)
@@ -8691,7 +9080,7 @@ links."
(point))
what "children")
(goto-char start)
- (show-subtree)
+ (outline-show-subtree)
(outline-next-heading))
(t
;; we will sort the top-level entries in this file
@@ -8707,7 +9096,7 @@ links."
(setq end (point-max))
(setq what "top-level")
(goto-char start)
- (show-all)))
+ (outline-show-all)))
(setq beg (point))
(when (>= beg end) (goto-char start) (user-error "Nothing to sort"))
@@ -8717,32 +9106,34 @@ links."
re (concat "^" (regexp-quote stars) " +")
re2 (concat "^" (regexp-quote (substring stars 0 -1)) "[ \t\n]")
txt (buffer-substring beg end))
- (if (not (equal (substring txt -1) "\n")) (setq txt (concat txt "\n")))
- (if (and (not (equal stars "*")) (string-match re2 txt))
- (user-error "Region to sort contains a level above the first entry"))
+ (unless (equal (substring txt -1) "\n") (setq txt (concat txt "\n")))
+ (when (and (not (equal stars "*")) (string-match re2 txt))
+ (user-error "Region to sort contains a level above the first entry"))
(unless sorting-type
(message
"Sort %s: [a]lpha [n]umeric [p]riority p[r]operty todo[o]rder [f]unc
- [t]ime [s]cheduled [d]eadline [c]reated
- A/N/P/R/O/F/T/S/D/C means reversed:"
+ [t]ime [s]cheduled [d]eadline [c]reated cloc[k]ing
+ A/N/P/R/O/F/T/S/D/C/K means reversed:"
what)
- (setq sorting-type (read-char-exclusive))
-
- (unless getkey-func
- (and (= (downcase sorting-type) ?f)
- (setq getkey-func
- (org-icompleting-read "Sort using function: "
- obarray 'fboundp t nil nil))
- (setq getkey-func (intern getkey-func))))
-
- (and (= (downcase sorting-type) ?r)
- (not property)
- (setq property
- (org-icompleting-read "Property: "
- (mapcar 'list (org-buffer-property-keys t))
- nil t))))
-
+ (setq sorting-type (read-char-exclusive)))
+
+ (unless getkey-func
+ (and (= (downcase sorting-type) ?f)
+ (setq getkey-func
+ (or (and interactive?
+ (org-read-function
+ "Function for extracting keys: "))
+ (error "Missing key extractor")))))
+
+ (and (= (downcase sorting-type) ?r)
+ (not property)
+ (setq property
+ (completing-read "Property: "
+ (mapcar #'list (org-buffer-property-keys t))
+ nil t)))
+
+ (when (member sorting-type '(?k ?K)) (org-clock-sum))
(message "Sorting entries...")
(save-restriction
@@ -8777,6 +9168,8 @@ links."
(if (looking-at org-complex-heading-regexp)
(funcall case-func (org-sort-remove-invisible (match-string 4)))
nil))
+ ((= dcst ?k)
+ (or (get-text-property (point) :org-clock-minutes) 0))
((= dcst ?t)
(let ((end (save-excursion (outline-next-heading) (point))))
(if (or (re-search-forward org-ts-regexp end t)
@@ -8807,22 +9200,29 @@ links."
((= dcst ?r)
(or (org-entry-get nil property) ""))
((= dcst ?o)
- (if (looking-at org-complex-heading-regexp)
- (- 9999 (length (member (match-string 2)
- org-todo-keywords-1)))))
+ (when (looking-at org-complex-heading-regexp)
+ (let* ((m (match-string 2))
+ (s (if (member m org-done-keywords) '- '+)))
+ (- 99 (funcall s (length (member m org-todo-keywords-1)))))))
((= dcst ?f)
(if getkey-func
(progn
(setq tmp (funcall getkey-func))
- (if (stringp tmp) (setq tmp (funcall case-func tmp)))
+ (when (stringp tmp) (setq tmp (funcall case-func tmp)))
tmp)
(error "Invalid key function `%s'" getkey-func)))
(t (error "Invalid sorting type `%c'" sorting-type))))
nil
(cond
((= dcst ?a) 'string<)
- ((= dcst ?f) compare-func)
- ((member dcst '(?p ?t ?s ?d ?c)) '<)))))
+ ((= dcst ?f)
+ (or compare-func
+ (and interactive?
+ (org-read-function
+ (concat "Function for comparing keys "
+ "(empty for default `sort-subr' predicate): ")
+ 'allow-empty))))
+ ((member dcst '(?p ?t ?s ?d ?c ?k)) '<)))))
(run-hooks 'org-after-sorting-entries-or-items-hook)
;; Reset the clock marker if needed
(when cmstr
@@ -8832,60 +9232,18 @@ links."
(move-marker org-clock-marker (point))))
(message "Sorting entries...done")))
-(defun org-do-sort (table what &optional with-case sorting-type)
- "Sort TABLE of WHAT according to SORTING-TYPE.
-The user will be prompted for the SORTING-TYPE if the call to this
-function does not specify it. WHAT is only for the prompt, to indicate
-what is being sorted. The sorting key will be extracted from
-the car of the elements of the table.
-If WITH-CASE is non-nil, the sorting will be case-sensitive."
- (unless sorting-type
- (message
- "Sort %s: [a]lphabetic, [n]umeric, [t]ime. A/N/T means reversed:"
- what)
- (setq sorting-type (read-char-exclusive)))
- (let ((dcst (downcase sorting-type))
- extractfun comparefun)
- ;; Define the appropriate functions
- (cond
- ((= dcst ?n)
- (setq extractfun 'string-to-number
- comparefun (if (= dcst sorting-type) '< '>)))
- ((= dcst ?a)
- (setq extractfun (if with-case (lambda(x) (org-sort-remove-invisible x))
- (lambda(x) (downcase (org-sort-remove-invisible x))))
- comparefun (if (= dcst sorting-type)
- 'string<
- (lambda (a b) (and (not (string< a b))
- (not (string= a b)))))))
- ((= dcst ?t)
- (setq extractfun
- (lambda (x)
- (if (or (string-match org-ts-regexp x)
- (string-match org-ts-regexp-both x))
- (float-time
- (org-time-string-to-time (match-string 0 x)))
- 0))
- comparefun (if (= dcst sorting-type) '< '>)))
- (t (error "Invalid sorting type `%c'" sorting-type)))
-
- (sort (mapcar (lambda (x) (cons (funcall extractfun (car x)) (cdr x)))
- table)
- (lambda (a b) (funcall comparefun (car a) (car b))))))
-
-
;;; The orgstruct minor mode
;; Define a minor mode which can be used in other modes in order to
-;; integrate the org-mode structure editing commands.
+;; integrate the Org mode structure editing commands.
-;; This is really a hack, because the org-mode structure commands use
+;; This is really a hack, because the Org mode structure commands use
;; keys which normally belong to the major mode. Here is how it
;; works: The minor mode defines all the keys necessary to operate the
;; structure commands, but wraps the commands into a function which
;; tests if the cursor is currently at a headline or a plain list
;; item. If that is the case, the structure command is used,
-;; temporarily setting many Org-mode variables like regular
+;; temporarily setting many Org mode variables like regular
;; expressions for filling etc. However, when any of those keys is
;; used at a different location, function uses `key-binding' to look
;; up if the key has an associated command in another currently active
@@ -8917,10 +9275,10 @@ orgstruct(++)-mode."
;;;###autoload
(define-minor-mode orgstruct-mode
"Toggle the minor mode `orgstruct-mode'.
-This mode is for using Org-mode structure commands in other
-modes. The following keys behave as if Org-mode were active, if
+This mode is for using Org mode structure commands in other
+modes. The following keys behave as if Org mode were active, if
the cursor is on a headline, or on a plain list item (both as
-defined by Org-mode)."
+defined by Org mode)."
nil " OrgStruct" (make-sparse-keymap)
(funcall (if orgstruct-mode
'add-to-invisibility-spec
@@ -8937,40 +9295,38 @@ defined by Org-mode)."
"Unconditionally turn on `orgstruct-mode'."
(orgstruct-mode 1))
-(defvar org-fb-vars nil)
-(make-variable-buffer-local 'org-fb-vars)
+(defvar-local orgstruct-is-++ nil
+ "Is `orgstruct-mode' in ++ version in the current-buffer?")
+(defvar-local org-fb-vars nil)
(defun orgstruct++-mode (&optional arg)
"Toggle `orgstruct-mode', the enhanced version of it.
In addition to setting orgstruct-mode, this also exports all
-indentation and autofilling variables from org-mode into the
+indentation and autofilling variables from Org mode into the
buffer. It will also recognize item context in multiline items."
(interactive "P")
(setq arg (prefix-numeric-value (or arg (if orgstruct-mode -1 1))))
(if (< arg 1)
(progn (orgstruct-mode -1)
- (mapc (lambda(v)
- (org-set-local (car v)
- (if (eq (car-safe (cadr v)) 'quote) (cadadr v) (cadr v))))
- org-fb-vars))
+ (dolist (v org-fb-vars)
+ (set (make-local-variable (car v))
+ (if (eq (car-safe (cadr v)) 'quote)
+ (cl-cadadr v)
+ (nth 1 v)))))
(orgstruct-mode 1)
(setq org-fb-vars nil)
(unless org-local-vars
(setq org-local-vars (org-get-local-variables)))
(let (var val)
- (mapc
- (lambda (x)
- (when (string-match
- "^\\(paragraph-\\|auto-fill\\|normal-auto-fill\\|fill-paragraph\\|fill-prefix\\|indent-\\)"
- (symbol-name (car x)))
- (setq var (car x) val (nth 1 x))
- (push (list var `(quote ,(eval var))) org-fb-vars)
- (org-set-local var (if (eq (car-safe val) 'quote) (nth 1 val) val))))
- org-local-vars)
- (org-set-local 'orgstruct-is-++ t))))
-
-(defvar orgstruct-is-++ nil
- "Is `orgstruct-mode' in ++ version in the current-buffer?")
-(make-variable-buffer-local 'orgstruct-is-++)
+ (dolist (x org-local-vars)
+ (when (string-match
+ "^\\(paragraph-\\|auto-fill\\|normal-auto-fill\\|fill-paragraph\
+\\|fill-prefix\\|indent-\\)"
+ (symbol-name (car x)))
+ (setq var (car x) val (nth 1 x))
+ (push (list var `(quote ,(eval var))) org-fb-vars)
+ (set (make-local-variable var)
+ (if (eq (car-safe val) 'quote) (nth 1 val) val))))
+ (setq-local orgstruct-is-++ t))))
;;;###autoload
(defun turn-on-orgstruct++ ()
@@ -8999,6 +9355,7 @@ buffer. It will also recognize item context in multiline items."
org-ctrl-c-minus
org-ctrl-c-star
org-cycle
+ org-force-cycle-archived
org-forward-heading-same-level
org-insert-heading
org-insert-heading-respect-content
@@ -9018,6 +9375,7 @@ buffer. It will also recognize item context in multiline items."
org-shifttab
org-shifttab
org-shiftup
+ org-show-children
org-show-subtree
org-sort
org-up-element
@@ -9025,8 +9383,7 @@ buffer. It will also recognize item context in multiline items."
outline-next-visible-heading
outline-previous-visible-heading
outline-promote
- outline-up-heading
- show-children))
+ outline-up-heading))
(let ((f (or (car-safe cell) cell))
(disable-when-heading-prefix (cdr-safe cell)))
(when (fboundp f)
@@ -9045,15 +9402,15 @@ buffer. It will also recognize item context in multiline items."
(regexp-quote (cdr rep))
(car rep)
(key-description binding)))))
- (pushnew binding new-bindings :test 'equal)))
+ (cl-pushnew binding new-bindings :test 'equal)))
(dolist (binding new-bindings)
(let ((key (lookup-key orgstruct-mode-map binding)))
(when (or (not key) (numberp key))
- (condition-case nil
- (org-defkey orgstruct-mode-map
- binding
- (orgstruct-make-binding f binding disable-when-heading-prefix))
- (error nil)))))))))
+ (ignore-errors
+ (org-defkey orgstruct-mode-map
+ binding
+ (orgstruct-make-binding
+ f binding disable-when-heading-prefix))))))))))
(run-hooks 'orgstruct-setup-hook))
(defun orgstruct-make-binding (fun key disable-when-heading-prefix)
@@ -9152,9 +9509,9 @@ definitions."
;; normalize contexts
(mapcar
(lambda(c) (cond ((listp (cadr c))
- (list (car c) (car c) (cadr c)))
+ (list (car c) (car c) (nth 1 c)))
((string= "" (cadr c))
- (list (car c) (car c) (caddr c)))
+ (list (car c) (car c) (nth 2 c)))
(t c)))
contexts))
(a alist) r s)
@@ -9168,7 +9525,7 @@ definitions."
(setq vrules (org-contextualize-validate-key
(car c) contexts)))
(mapc (lambda (vr)
- (when (not (equal (car vr) (cadr vr)))
+ (unless (equal (car vr) (cadr vr))
(setq repl vr)))
vrules)
(if (not repl) (push c r)
@@ -9185,39 +9542,37 @@ definitions."
(delete-dups
(mapcar (lambda (x)
(let ((tpl (car x)))
- (when (not (delq
- nil
- (mapcar (lambda (y)
- (equal y tpl))
- s)))
+ (unless (delq
+ nil
+ (mapcar (lambda (y)
+ (equal y tpl))
+ s))
x)))
(reverse r))))))
(defun org-contextualize-validate-key (key contexts)
"Check CONTEXTS for agenda or capture KEY."
- (let (rr res)
+ (let (res)
(dolist (r contexts)
- (mapc
- (lambda (rr)
- (when
- (and (equal key (car r))
- (if (functionp rr) (funcall rr)
- (or (and (eq (car rr) 'in-file)
- (buffer-file-name)
- (string-match (cdr rr) (buffer-file-name)))
- (and (eq (car rr) 'in-mode)
- (string-match (cdr rr) (symbol-name major-mode)))
- (and (eq (car rr) 'in-buffer)
- (string-match (cdr rr) (buffer-name)))
- (when (and (eq (car rr) 'not-in-file)
- (buffer-file-name))
- (not (string-match (cdr rr) (buffer-file-name))))
- (when (eq (car rr) 'not-in-mode)
- (not (string-match (cdr rr) (symbol-name major-mode))))
- (when (eq (car rr) 'not-in-buffer)
- (not (string-match (cdr rr) (buffer-name)))))))
- (push r res)))
- (car (last r))))
+ (dolist (rr (car (last r)))
+ (when
+ (and (equal key (car r))
+ (if (functionp rr) (funcall rr)
+ (or (and (eq (car rr) 'in-file)
+ (buffer-file-name)
+ (string-match (cdr rr) (buffer-file-name)))
+ (and (eq (car rr) 'in-mode)
+ (string-match (cdr rr) (symbol-name major-mode)))
+ (and (eq (car rr) 'in-buffer)
+ (string-match (cdr rr) (buffer-name)))
+ (when (and (eq (car rr) 'not-in-file)
+ (buffer-file-name))
+ (not (string-match (cdr rr) (buffer-file-name))))
+ (when (eq (car rr) 'not-in-mode)
+ (not (string-match (cdr rr) (symbol-name major-mode))))
+ (when (eq (car rr) 'not-in-buffer)
+ (not (string-match (cdr rr) (buffer-name)))))))
+ (push r res))))
(delete-dups (delq nil res))))
(defun org-context-p (&rest contexts)
@@ -9235,45 +9590,47 @@ Possible values in the list of contexts are `table', `headline', and `item'."
(org-in-item-p)))
(goto-char pos))))
+(defconst org-unique-local-variables
+ '(org-element--cache
+ org-element--cache-objects
+ org-element--cache-sync-keys
+ org-element--cache-sync-requests
+ org-element--cache-sync-timer)
+ "List of local variables that cannot be transferred to another buffer.")
+
(defun org-get-local-variables ()
"Return a list of all local variables in an Org mode buffer."
- (let (varlist)
- (with-current-buffer (get-buffer-create "*Org tmp*")
- (erase-buffer)
- (org-mode)
- (setq varlist (buffer-local-variables)))
- (kill-buffer "*Org tmp*")
- (delq nil
- (mapcar
- (lambda (x)
- (setq x
- (if (symbolp x)
- (list x)
- (list (car x) (cdr x))))
- (if (and (not (get (car x) 'org-state))
- (string-match
- "^\\(org-\\|orgtbl-\\|outline-\\|comment-\\|paragraph-\\|auto-fill\\|normal-auto-fill\\|fill-paragraph\\|indent-\\)"
- (symbol-name (car x))))
- x nil))
- varlist))))
+ (delq nil
+ (mapcar
+ (lambda (x)
+ (let* ((binding (if (symbolp x) (list x) (list (car x) (cdr x))))
+ (name (car binding)))
+ (and (not (get name 'org-state))
+ (not (memq name org-unique-local-variables))
+ (string-match-p
+ "\\`\\(org-\\|orgtbl-\\|outline-\\|comment-\\|paragraph-\\|\
+auto-fill\\|normal-auto-fill\\|fill-paragraph\\|indent-\\)"
+ (symbol-name name))
+ binding)))
+ (with-temp-buffer
+ (org-mode)
+ (buffer-local-variables)))))
(defun org-clone-local-variables (from-buffer &optional regexp)
"Clone local variables from FROM-BUFFER.
Optional argument REGEXP selects variables to clone."
- (mapc
- (lambda (pair)
- (and (symbolp (car pair))
- (or (null regexp)
- (string-match regexp (symbol-name (car pair))))
- (set (make-local-variable (car pair))
- (cdr pair))))
- (buffer-local-variables from-buffer)))
+ (dolist (pair (buffer-local-variables from-buffer))
+ (pcase pair
+ (`(,name . ,value) ;ignore unbound variables
+ (when (and (not (memq name org-unique-local-variables))
+ (or (null regexp) (string-match-p regexp (symbol-name name))))
+ (set (make-local-variable name) value))))))
;;;###autoload
(defun org-run-like-in-org-mode (cmd)
- "Run a command, pretending that the current buffer is in Org-mode.
+ "Run a command, pretending that the current buffer is in Org mode.
This will temporarily bind local variables that are typically bound in
-Org-mode to the values they have in Org-mode, and then interactively
+Org mode to the values they have in Org mode, and then interactively
call CMD."
(org-load-modules-maybe)
(unless org-local-vars
@@ -9287,67 +9644,119 @@ call CMD."
(eval `(let ,binds
(call-interactively (quote ,cmd))))))
-;;;; Archiving
-
(defun org-get-category (&optional pos force-refresh)
"Get the category applying to position POS."
(save-match-data
- (if force-refresh (org-refresh-category-properties))
+ (when force-refresh (org-refresh-category-properties))
(let ((pos (or pos (point))))
(or (get-text-property pos 'org-category)
(progn (org-refresh-category-properties)
(get-text-property pos 'org-category))))))
-(defun org-refresh-category-properties ()
- "Refresh category text properties in the buffer."
- (let ((case-fold-search t)
- (inhibit-read-only t)
- (def-cat (cond
- ((null org-category)
- (if buffer-file-name
- (file-name-sans-extension
- (file-name-nondirectory buffer-file-name))
- "???"))
- ((symbolp org-category) (symbol-name org-category))
- (t org-category)))
- beg end cat pos optionp)
- (org-with-silent-modifications
- (save-excursion
- (save-restriction
- (widen)
- (goto-char (point-min))
- (put-text-property (point) (point-max) 'org-category def-cat)
- (while (re-search-forward
- "^\\(#\\+CATEGORY:\\|[ \t]*:CATEGORY:\\)\\(.*\\)" nil t)
- (setq pos (match-end 0)
- optionp (equal (char-after (match-beginning 0)) ?#)
- cat (org-trim (match-string 2)))
- (if optionp
- (setq beg (point-at-bol) end (point-max))
- (org-back-to-heading t)
- (setq beg (point) end (org-end-of-subtree t t)))
- (put-text-property beg end 'org-category cat)
- (put-text-property beg end 'org-category-position beg)
- (goto-char pos)))))))
+;;; Refresh properties
(defun org-refresh-properties (dprop tprop)
"Refresh buffer text properties.
-DPROP is the drawer property and TPROP is the corresponding text
-property to set."
- (let ((case-fold-search t)
- (inhibit-read-only t) p)
+DPROP is the drawer property and TPROP is either the
+corresponding text property to set, or an alist with each element
+being a text property (as a symbol) and a function to apply to
+the value of the drawer property."
+ (let* ((case-fold-search t)
+ (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))))
(org-with-silent-modifications
- (save-excursion
- (save-restriction
- (widen)
- (goto-char (point-min))
- (while (re-search-forward (concat "^[ \t]*:" dprop ": +\\(.*\\)[ \t]*$") nil t)
- (setq p (org-match-string-no-properties 1))
- (save-excursion
- (org-back-to-heading t)
- (put-text-property
- (point-at-bol) (or (outline-next-heading) (point-max)) tprop p))))))))
+ (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.
+ (while (re-search-forward property-re nil t)
+ (when (org-at-property-p)
+ (org-refresh-property tprop (org-entry-get (point) dprop) inherit?))
+ (outline-next-heading))))))
+
+(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))))))))
+(defun org-refresh-category-properties ()
+ "Refresh category text properties in the buffer."
+ (let ((case-fold-search t)
+ (inhibit-read-only t)
+ (default-category
+ (cond ((null org-category)
+ (if buffer-file-name
+ (file-name-sans-extension
+ (file-name-nondirectory buffer-file-name))
+ "???"))
+ ((symbolp org-category) (symbol-name org-category))
+ (t org-category))))
+ (org-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.
+ (put-text-property
+ (point-min) (point-max)
+ 'org-category
+ (catch 'buffer-category
+ (goto-char (point-max))
+ (while (re-search-backward "^[ \t]*#\\+CATEGORY:" (point-min) t)
+ (let ((element (org-element-at-point)))
+ (when (eq (org-element-type element) 'keyword)
+ (throw 'buffer-category
+ (org-element-property :value element)))))
+ default-category))
+ ;; Set sub-tree specific categories.
+ (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))
+ 'org-category
+ value)))))))))
+
+(defun org-refresh-stats-properties ()
+ "Refresh stats text properties in the buffer."
+ (org-with-silent-modifications
+ (org-with-point-at 1
+ (let ((regexp (concat org-outline-regexp-bol
+ ".*\\[\\([0-9]*\\)\\(?:%\\|/\\([0-9]*\\)\\)\\]")))
+ (while (re-search-forward regexp nil t)
+ (let* ((numerator (string-to-number (match-string 1)))
+ (denominator (and (match-end 2)
+ (string-to-number (match-string 2))))
+ (stats (cond ((not denominator) numerator) ;percent
+ ((= denominator 0) 0)
+ (t (/ (* numerator 100) denominator)))))
+ (put-text-property (point) (progn (org-end-of-subtree t t) (point))
+ 'org-stats stats)))))))
+
+(defun org-refresh-effort-properties ()
+ "Refresh effort properties"
+ (org-refresh-properties
+ org-effort-property
+ '((effort . identity)
+ (effort-minutes . org-duration-string-to-minutes))))
;;;; Link Stuff
@@ -9387,78 +9796,54 @@ property to set."
(defvar org-store-link-plist nil
"Plist with info about the most recently link created with `org-store-link'.")
-(defvar org-link-protocols nil
- "Link protocols added to Org-mode using `org-add-link-type'.")
+(defun org-store-link-functions ()
+ "Return a list of functions that are called to create and store a link.
+The functions defined in the :store property of
+`org-link-parameters'.
-(defvar org-store-link-functions nil
- "List of functions that are called to create and store a link.
Each function will be called in turn until one returns a non-nil
-value. Each function should check if it is responsible for creating
-this link (for example by looking at the major mode).
-If not, it must exit and return nil.
-If yes, it should return a non-nil value after a calling
-`org-store-link-props' with a list of properties and values.
-Special properties are:
+value. Each function should check if it is responsible for
+creating this link (for example by looking at the major mode).
+If not, it must exit and return nil. If yes, it should return
+a non-nil value after calling `org-store-link-props' with a list
+of properties and values. Special properties are:
:type The link prefix, like \"http\". This must be given.
:link The link, like \"http://www.astro.uva.nl/~dominik\".
This is obligatory as well.
:description Optional default description for the second pair
- of brackets in an Org-mode link. The user can still change
- this when inserting this link into an Org-mode buffer.
+ of brackets in an Org mode link. The user can still change
+ this when inserting this link into an Org mode buffer.
In addition to these, any additional properties can be specified
-and then used in capture templates.")
-
-(defun org-add-link-type (type &optional follow export)
- "Add TYPE to the list of `org-link-types'.
-Re-compute all regular expressions depending on `org-link-types'
-
-FOLLOW and EXPORT are two functions.
-
-FOLLOW should take the link path as the single argument and do whatever
-is necessary to follow the link, for example find a file or display
-a mail message.
-
-EXPORT should format the link path for export to one of the export formats.
-It should be a function accepting three arguments:
-
- path the path of the link, the text after the prefix (like \"http:\")
- desc the description of the link, if any, or a description added by
- org-export-normalize-links if there is none
- format the export format, a symbol like `html' or `latex' or `ascii'..
-
-The function may use the FORMAT information to return different values
-depending on the format. The return value will be put literally into
-the exported file. If the return value is nil, this means Org should
-do what it normally does with links which do not have EXPORT defined.
-
-Org-mode has a built-in default for exporting links. If you are happy with
-this default, there is no need to define an export function for the link
-type. For a simple example of an export function, see `org-bbdb.el'."
- (add-to-list 'org-link-types type t)
- (org-make-link-regexps)
- (if (assoc type org-link-protocols)
- (setcdr (assoc type org-link-protocols) (list follow export))
- (push (list type follow export) org-link-protocols)))
+and then used in capture templates."
+ (cl-loop for link in org-link-parameters
+ with store-func
+ do (setq store-func (org-link-get-parameter (car link) :store))
+ if store-func
+ collect store-func))
(defvar org-agenda-buffer-name) ; Defined in org-agenda.el
(defvar org-id-link-to-org-use-id) ; Defined in org-id.el
;;;###autoload
(defun org-store-link (arg)
- "\\Store an org-link to the current location.
+ "Store an org-link to the current location.
+\\
This link is added to `org-stored-links' and can later be inserted
-into an org-buffer with \\[org-insert-link].
+into an Org buffer with `org-insert-link' (`\\[org-insert-link]').
-For some link types, a prefix arg is interpreted.
-For links to Usenet articles, arg negates `org-gnus-prefer-web-links'.
-For file links, arg negates `org-context-in-file-links'.
+For some link types, a `\\[universal-argument]' prefix ARG is interpreted. \
+A single
+`\\[universal-argument]' negates `org-context-in-file-links' for file links or
+`org-gnus-prefer-web-links' for links to Usenet articles.
-A double prefix arg force skipping storing functions that are not
-part of Org's core.
+A `\\[universal-argument] \\[universal-argument]' prefix ARG forces \
+skipping storing functions that are not
+part of Org core.
-A triple prefix arg force storing a link for each line in the
+A `\\[universal-argument] \\[universal-argument] \\[universal-argument]' \
+prefix ARG forces storing a link for each line in the
active region."
(interactive "P")
(org-load-modules-maybe)
@@ -9473,111 +9858,111 @@ active region."
(call-interactively 'org-store-link))
(move-beginning-of-line 2)
(set-mark (point)))))
- (org-with-limited-levels
- (setq org-store-link-plist nil)
- (let (link cpltxt desc description search
- txt custom-id agenda-link sfuns sfunsn)
- (cond
+ (setq org-store-link-plist nil)
+ (let (link cpltxt desc description search
+ txt custom-id agenda-link sfuns sfunsn)
+ (cond
- ;; Store a link using an external link type
- ((and (not (equal arg '(16)))
- (setq sfuns
- (delq
- nil (mapcar (lambda (f)
- (let (fs) (if (funcall f) (push f fs))))
- org-store-link-functions))
- sfunsn (mapcar (lambda (fu) (symbol-name (car fu))) sfuns))
- (or (and (cdr sfuns)
- (funcall (intern
- (completing-read
- "Which function for creating the link? "
- sfunsn nil t (car sfunsn)))))
- (funcall (caar sfuns)))
- (setq link (plist-get org-store-link-plist :link)
- desc (or (plist-get org-store-link-plist
- :description)
- link))))
-
- ;; Store a link from a source code buffer
- ((org-src-edit-buffer-p)
- (let (label gc)
- (while (or (not label)
- (save-excursion
- (save-restriction
- (widen)
- (goto-char (point-min))
- (re-search-forward
- (regexp-quote (format org-coderef-label-format label))
- nil t))))
- (when label (message "Label exists already") (sit-for 2))
- (setq label (read-string "Code line label: " label)))
- (end-of-line 1)
- (setq link (format org-coderef-label-format label))
- (setq gc (- 79 (length link)))
- (if (< (current-column) gc) (org-move-to-column gc t) (insert " "))
- (insert link)
- (setq link (concat "(" label ")") desc nil)))
-
- ;; We are in the agenda, link to referenced location
- ((equal (org-bound-and-true-p org-agenda-buffer-name) (buffer-name))
- (let ((m (or (get-text-property (point) 'org-hd-marker)
- (get-text-property (point) 'org-marker))))
- (when m
- (org-with-point-at m
- (setq agenda-link
- (if (org-called-interactively-p 'any)
- (call-interactively 'org-store-link)
- (org-store-link nil)))))))
-
- ((eq major-mode 'calendar-mode)
- (let ((cd (calendar-cursor-to-date)))
- (setq link
- (format-time-string
- (car org-time-stamp-formats)
- (apply 'encode-time
- (list 0 0 0 (nth 1 cd) (nth 0 cd) (nth 2 cd)
- nil nil nil))))
- (org-store-link-props :type "calendar" :date cd)))
-
- ((eq major-mode 'help-mode)
- (setq link (concat "help:" (save-excursion
- (goto-char (point-min))
- (looking-at "^[^ ]+")
- (match-string 0))))
- (org-store-link-props :type "help"))
-
- ((eq major-mode 'w3-mode)
- (setq cpltxt (if (and (buffer-name)
- (not (string-match "Untitled" (buffer-name))))
- (buffer-name)
- (url-view-url t))
- link (url-view-url t))
- (org-store-link-props :type "w3" :url (url-view-url t)))
-
- ((eq major-mode 'image-mode)
- (setq cpltxt (concat "file:"
- (abbreviate-file-name buffer-file-name))
- link cpltxt)
- (org-store-link-props :type "image" :file buffer-file-name))
-
- ;; In dired, store a link to the file of the current line
- ((eq major-mode 'dired-mode)
- (let ((file (dired-get-filename nil t)))
- (setq file (if file
- (abbreviate-file-name
- (expand-file-name (dired-get-filename nil t)))
- ;; otherwise, no file so use current directory.
- default-directory))
- (setq cpltxt (concat "file:" file)
- link cpltxt)))
-
- ((setq search (run-hook-with-args-until-success
- 'org-create-file-search-functions))
- (setq link (concat "file:" (abbreviate-file-name buffer-file-name)
- "::" search))
- (setq cpltxt (or description link)))
-
- ((and (buffer-file-name (buffer-base-buffer)) (derived-mode-p 'org-mode))
+ ;; Store a link using an external link type
+ ((and (not (equal arg '(16)))
+ (setq sfuns
+ (delq
+ nil (mapcar (lambda (f)
+ (let (fs) (if (funcall f) (push f fs))))
+ (org-store-link-functions)))
+ sfunsn (mapcar (lambda (fu) (symbol-name (car fu))) sfuns))
+ (or (and (cdr sfuns)
+ (funcall (intern
+ (completing-read
+ "Which function for creating the link? "
+ sfunsn nil t (car sfunsn)))))
+ (funcall (caar sfuns)))
+ (setq link (plist-get org-store-link-plist :link)
+ desc (or (plist-get org-store-link-plist
+ :description)
+ link))))
+
+ ;; Store a link from a source code buffer.
+ ((org-src-edit-buffer-p)
+ (let ((coderef-format (org-src-coderef-format)))
+ (cond ((save-excursion
+ (beginning-of-line)
+ (looking-at (org-src-coderef-regexp coderef-format)))
+ (setq link (format "(%s)" (match-string-no-properties 3))))
+ ((called-interactively-p 'any)
+ (let ((label (read-string "Code line label: ")))
+ (end-of-line)
+ (setq link (format coderef-format label))
+ (let ((gc (- 79 (length link))))
+ (if (< (current-column) gc)
+ (org-move-to-column gc t)
+ (insert " ")))
+ (insert link)
+ (setq link (concat "(" label ")"))
+ (setq desc nil)))
+ (t (setq link nil)))))
+
+ ;; We are in the agenda, link to referenced location
+ ((equal (bound-and-true-p org-agenda-buffer-name) (buffer-name))
+ (let ((m (or (get-text-property (point) 'org-hd-marker)
+ (get-text-property (point) 'org-marker))))
+ (when m
+ (org-with-point-at m
+ (setq agenda-link
+ (if (called-interactively-p 'any)
+ (call-interactively 'org-store-link)
+ (org-store-link nil)))))))
+
+ ((eq major-mode 'calendar-mode)
+ (let ((cd (calendar-cursor-to-date)))
+ (setq link
+ (format-time-string
+ (car org-time-stamp-formats)
+ (apply 'encode-time
+ (list 0 0 0 (nth 1 cd) (nth 0 cd) (nth 2 cd)
+ nil nil nil))))
+ (org-store-link-props :type "calendar" :date cd)))
+
+ ((eq major-mode 'help-mode)
+ (setq link (concat "help:" (save-excursion
+ (goto-char (point-min))
+ (looking-at "^[^ ]+")
+ (match-string 0))))
+ (org-store-link-props :type "help"))
+
+ ((eq major-mode 'w3-mode)
+ (setq cpltxt (if (and (buffer-name)
+ (not (string-match "Untitled" (buffer-name))))
+ (buffer-name)
+ (url-view-url t))
+ link (url-view-url t))
+ (org-store-link-props :type "w3" :url (url-view-url t)))
+
+ ((eq major-mode 'image-mode)
+ (setq cpltxt (concat "file:"
+ (abbreviate-file-name buffer-file-name))
+ link cpltxt)
+ (org-store-link-props :type "image" :file buffer-file-name))
+
+ ;; In dired, store a link to the file of the current line
+ ((derived-mode-p 'dired-mode)
+ (let ((file (dired-get-filename nil t)))
+ (setq file (if file
+ (abbreviate-file-name
+ (expand-file-name (dired-get-filename nil t)))
+ ;; otherwise, no file so use current directory.
+ default-directory))
+ (setq cpltxt (concat "file:" file)
+ link cpltxt)))
+
+ ((setq search (run-hook-with-args-until-success
+ 'org-create-file-search-functions))
+ (setq link (concat "file:" (abbreviate-file-name buffer-file-name)
+ "::" search))
+ (setq cpltxt (or description link)))
+
+ ((and (buffer-file-name (buffer-base-buffer)) (derived-mode-p 'org-mode))
+ (org-with-limited-levels
(setq custom-id (org-entry-get nil "CUSTOM_ID"))
(cond
;; Store a link using the target at point
@@ -9590,7 +9975,7 @@ active region."
link cpltxt))
((and (featurep 'org-id)
(or (eq org-id-link-to-org-use-id t)
- (and (org-called-interactively-p 'any)
+ (and (called-interactively-p 'any)
(or (eq org-id-link-to-org-use-id 'create-if-interactive)
(and (eq org-id-link-to-org-use-id
'create-if-interactive-and-no-custom-id)
@@ -9613,15 +9998,13 @@ active region."
(abbreviate-file-name
(buffer-file-name (buffer-base-buffer)))))
;; Add a context search string
- (when (org-xor org-context-in-file-links arg)
- (let* ((ee (org-element-at-point))
- (et (org-element-type ee))
- (ev (plist-get (cadr ee) :value))
- (ek (plist-get (cadr ee) :key))
- (eok (and (stringp ek) (string-match "name" ek))))
+ (when (org-xor org-context-in-file-links
+ (equal arg '(4)))
+ (let* ((element (org-element-at-point))
+ (name (org-element-property :name element)))
(setq txt (cond
((org-at-heading-p) nil)
- ((and (eq et 'keyword) eok) ev)
+ (name)
((org-region-active-p)
(buffer-substring (region-beginning) (region-end)))))
(when (or (null txt) (string-match "\\S-" txt))
@@ -9630,74 +10013,80 @@ active region."
(condition-case nil
(org-make-org-heading-search-string txt)
(error "")))
- desc (or (and (eq et 'keyword) eok ev)
+ desc (or name
(nth 4 (ignore-errors (org-heading-components)))
"NONE")))))
- (if (string-match "::\\'" cpltxt)
- (setq cpltxt (substring cpltxt 0 -2)))
- (setq link cpltxt))))
-
- ((buffer-file-name (buffer-base-buffer))
- ;; Just link to this file here.
- (setq cpltxt (concat "file:"
- (abbreviate-file-name
- (buffer-file-name (buffer-base-buffer)))))
- ;; Add a context string.
- (when (org-xor org-context-in-file-links arg)
- (setq txt (if (org-region-active-p)
- (buffer-substring (region-beginning) (region-end))
- (buffer-substring (point-at-bol) (point-at-eol))))
- ;; Only use search option if there is some text.
- (when (string-match "\\S-" txt)
- (setq cpltxt
- (concat cpltxt "::" (org-make-org-heading-search-string txt))
- desc "NONE")))
- (setq link cpltxt))
-
- ((org-called-interactively-p 'interactive)
- (user-error "No method for storing a link from this buffer"))
-
- (t (setq link nil)))
-
- ;; We're done setting link and desc, clean up
- (if (consp link) (setq cpltxt (car link) link (cdr link)))
- (setq link (or link cpltxt)
- desc (or desc cpltxt))
- (cond ((equal desc "NONE") (setq desc nil))
- ((and desc (string-match org-bracket-link-analytic-regexp desc))
- (let ((d0 (match-string 3 desc))
- (p0 (match-string 5 desc)))
- (setq desc
- (replace-regexp-in-string
- org-bracket-link-regexp
- (concat (or p0 d0)
- (if (equal (length (match-string 0 desc))
- (length desc)) "*" "")) desc)))))
-
- ;; Return the link
- (if (not (and (or (org-called-interactively-p 'any)
- executing-kbd-macro)
- link))
- (or agenda-link (and link (org-make-link-string link desc)))
- (push (list link desc) org-stored-links)
- (message "Stored: %s" (or desc link))
- (when custom-id
- (setq link (concat "file:" (abbreviate-file-name
- (buffer-file-name)) "::#" custom-id))
- (push (list link desc) org-stored-links))
- (car org-stored-links))))))
+ (when (string-match "::\\'" cpltxt)
+ (setq cpltxt (substring cpltxt 0 -2)))
+ (setq link cpltxt)))))
+
+ ((buffer-file-name (buffer-base-buffer))
+ ;; Just link to this file here.
+ (setq cpltxt (concat "file:"
+ (abbreviate-file-name
+ (buffer-file-name (buffer-base-buffer)))))
+ ;; Add a context string.
+ (when (org-xor org-context-in-file-links
+ (equal arg '(4)))
+ (setq txt (if (org-region-active-p)
+ (buffer-substring (region-beginning) (region-end))
+ (buffer-substring (point-at-bol) (point-at-eol))))
+ ;; Only use search option if there is some text.
+ (when (string-match "\\S-" txt)
+ (setq cpltxt
+ (concat cpltxt "::" (org-make-org-heading-search-string txt))
+ desc "NONE")))
+ (setq link cpltxt))
+
+ ((called-interactively-p 'interactive)
+ (user-error "No method for storing a link from this buffer"))
+
+ (t (setq link nil)))
+
+ ;; We're done setting link and desc, clean up
+ (when (consp link) (setq cpltxt (car link) link (cdr link)))
+ (setq link (or link cpltxt)
+ desc (or desc cpltxt))
+ (cond ((not desc))
+ ((equal desc "NONE") (setq desc nil))
+ (t (setq desc
+ (replace-regexp-in-string
+ org-bracket-link-analytic-regexp
+ (lambda (m) (or (match-string 5 m) (match-string 3 m)))
+ desc))))
+ ;; Return the link
+ (if (not (and (or (called-interactively-p 'any)
+ executing-kbd-macro)
+ link))
+ (or agenda-link (and link (org-make-link-string link desc)))
+ (push (list link desc) org-stored-links)
+ (message "Stored: %s" (or desc link))
+ (when custom-id
+ (setq link (concat "file:" (abbreviate-file-name
+ (buffer-file-name)) "::#" custom-id))
+ (push (list link desc) org-stored-links))
+ (car org-stored-links)))))
(defun org-store-link-props (&rest plist)
- "Store link properties, extract names and addresses."
- (let (x adr)
- (when (setq x (plist-get plist :from))
- (setq adr (mail-extract-address-components x))
- (setq plist (plist-put plist :fromname (car adr)))
- (setq plist (plist-put plist :fromaddress (nth 1 adr))))
- (when (setq x (plist-get plist :to))
- (setq adr (mail-extract-address-components x))
- (setq plist (plist-put plist :toname (car adr)))
- (setq plist (plist-put plist :toaddress (nth 1 adr)))))
+ "Store link properties, extract names, addresses and dates."
+ (let ((x (plist-get plist :from)))
+ (when x
+ (let ((adr (mail-extract-address-components x)))
+ (setq plist (plist-put plist :fromname (car adr)))
+ (setq plist (plist-put plist :fromaddress (nth 1 adr))))))
+ (let ((x (plist-get plist :to)))
+ (when x
+ (let ((adr (mail-extract-address-components x)))
+ (setq plist (plist-put plist :toname (car adr)))
+ (setq plist (plist-put plist :toaddress (nth 1 adr))))))
+ (let ((x (ignore-errors (date-to-time (plist-get plist :date)))))
+ (when x
+ (setq plist (plist-put plist :date-timestamp
+ (format-time-string
+ (org-time-stamp-format t) x)))
+ (setq plist (plist-put plist :date-timestamp-inactive
+ (format-time-string
+ (org-time-stamp-format t t) x)))))
(let ((from (plist-get plist :from))
(to (plist-get plist :to)))
(when (and from to org-from-is-user-regexp)
@@ -9763,45 +10152,34 @@ according to FMT (default from `org-email-link-description-format')."
(defun org-make-link-string (link &optional description)
"Make a link with brackets, consisting of LINK and DESCRIPTION."
- (unless (string-match "\\S-" link)
- (error "Empty link"))
- (when (and description
- (stringp description)
- (not (string-match "\\S-" description)))
- (setq description nil))
- (when (stringp description)
- ;; Remove brackets from the description, they are fatal.
- (while (string-match "\\[" description)
- (setq description (replace-match "{" t t description)))
- (while (string-match "\\]" description)
- (setq description (replace-match "}" t t description))))
- (when (equal link description)
- ;; No description needed, it is identical
- (setq description nil))
- (when (and (not description)
- (not (string-match (org-image-file-name-regexp) link))
- (not (equal link (org-link-escape link))))
- (setq description (org-extract-attributes link)))
- (setq link
- (cond ((string-match (org-image-file-name-regexp) link) link)
- ((string-match org-link-types-re link)
- (concat (match-string 1 link)
- (org-link-escape (substring link (match-end 1)))))
- (t (org-link-escape link))))
- (concat "[[" link "]"
- (if description (concat "[" description "]") "")
- "]"))
+ (unless (org-string-nw-p link) (error "Empty link"))
+ (let ((uri (cond ((string-match org-link-types-re link)
+ (concat (match-string 1 link)
+ (org-link-escape (substring link (match-end 1)))))
+ ;; For readability, url-encode internal links only
+ ;; when absolutely needed (i.e, when they contain
+ ;; square brackets). File links however, are
+ ;; encoded since, e.g., spaces are significant.
+ ((or (file-name-absolute-p link)
+ (string-match-p "\\`\\.\\.?/\\|[][]" link))
+ (org-link-escape link))
+ (t link)))
+ (description
+ (and (org-string-nw-p description)
+ ;; Remove brackets from description, as they are fatal.
+ (replace-regexp-in-string
+ "[][]" (lambda (m) (if (equal "[" m) "{" "}"))
+ (org-trim description)))))
+ (format "[[%s]%s]"
+ uri
+ (if description (format "[%s]" description) ""))))
(defconst org-link-escape-chars
- '(?\ ?\[ ?\] ?\; ?\= ?\+)
- "List of characters that should be escaped in link.
+ ;;%20 %5B %5D %25
+ '(?\s ?\[ ?\] ?%)
+ "List of characters that should be escaped in a link when stored to Org.
This is the list that is used for internal purposes.")
-(defconst org-link-escape-chars-browser
- '(?\ ?\")
- "List of escapes for characters that are problematic in links.
-This is the list that is used before handing over to the browser.")
-
(defun org-link-escape (text &optional table merge)
"Return percent escaped representation of TEXT.
TEXT is a string with the text to escape.
@@ -9809,35 +10187,29 @@ Optional argument TABLE is a list with characters that should be
escaped. When nil, `org-link-escape-chars' is used.
If optional argument MERGE is set, merge TABLE into
`org-link-escape-chars'."
- (cond
- ((and table merge)
- (mapc (lambda (defchr)
- (unless (member defchr table)
- (setq table (cons defchr table)))) org-link-escape-chars))
- ((null table)
- (setq table org-link-escape-chars)))
- (mapconcat
- (lambda (char)
- (if (or (member char table)
- (and (or (< char 32) (= char 37) (> char 126))
- org-url-hexify-p))
- (mapconcat (lambda (sequence-element)
- (format "%%%.2X" sequence-element))
- (or (encode-coding-char char 'utf-8)
- (error "Unable to percent escape character: %s"
- (char-to-string char))) "")
- (char-to-string char))) text ""))
+ (let ((characters-to-encode
+ (cond ((null table) org-link-escape-chars)
+ (merge (append org-link-escape-chars table))
+ (t table))))
+ (mapconcat
+ (lambda (c)
+ (if (or (memq c characters-to-encode)
+ (and org-url-hexify-p (or (< c 32) (> c 126))))
+ (mapconcat (lambda (e) (format "%%%.2X" e))
+ (or (encode-coding-char c 'utf-8)
+ (error "Unable to percent escape character: %c" c))
+ "")
+ (char-to-string c)))
+ text "")))
(defun org-link-unescape (str)
- "Unhex hexified Unicode strings as returned from the JavaScript function
-encodeURIComponent. E.g. `%C3%B6' is the german o-Umlaut."
- (unless (and (null str) (string= "" str))
- (let ((pos 0) (case-fold-search t) unhexed)
- (while (setq pos (string-match "\\(%[0-9a-f][0-9a-f]\\)+" str pos))
- (setq unhexed (org-link-unescape-compound (match-string 0 str)))
- (setq str (replace-match unhexed t t str))
- (setq pos (+ pos (length unhexed))))))
- str)
+ "Unhex hexified Unicode parts in string STR.
+E.g. `%C3%B6' becomes the german o-Umlaut. This is the
+reciprocal of `org-link-escape', which see."
+ (if (org-string-nw-p str)
+ (replace-regexp-in-string
+ "\\(%[0-9A-Za-z]\\{2\\}\\)+" #'org-link-unescape-compound str t t)
+ str))
(defun org-link-unescape-compound (hex)
"Unhexify Unicode hex-chars. E.g. `%C3%B6' is the German o-Umlaut.
@@ -9860,18 +10232,17 @@ Note: this function also decodes single byte encodings like
((>= val 192) (cons 2 192))
(t (cons 0 0)))
(cons 6 128))))
- (if (>= val 192) (setq eat (car shift-xor)))
+ (when (>= val 192) (setq eat (car shift-xor)))
(setq val (logxor val (cdr shift-xor)))
(setq sum (+ (lsh sum (car shift-xor)) val))
- (if (> eat 0) (setq eat (- eat 1)))
+ (when (> eat 0) (setq eat (- eat 1)))
(cond
((= 0 eat) ;multi byte
- (setq ret (concat ret (org-char-to-string sum)))
+ (setq ret (concat ret (char-to-string sum)))
(setq sum 0))
((not bytes) ; single byte(s)
- (setq ret (org-link-unescape-single-byte-sequence hex))))
- )) ;; end (while bytes
- ret )))
+ (setq ret (org-link-unescape-single-byte-sequence hex))))))
+ ret)))
(defun org-link-unescape-single-byte-sequence (hex)
"Unhexify hex-encoded single byte character sequences."
@@ -9901,28 +10272,47 @@ Note: this function also decodes single byte encodings like
(defun org-link-prettify (link)
"Return a human-readable representation of LINK.
-The car of LINK must be a raw link the cdr of LINK must be either
-a link description or nil."
+The car of LINK must be a raw link.
+The cdr of LINK must be either a link description or nil."
(let ((desc (or (cadr link) "")))
(concat (format "%-45s" (substring desc 0 (min (length desc) 40)))
"<" (car link) ">")))
;;;###autoload
(defun org-insert-link-global ()
- "Insert a link like Org-mode does.
-This command can be called in any mode to insert a link in Org-mode syntax."
+ "Insert a link like Org mode does.
+This command can be called in any mode to insert a link in Org syntax."
(interactive)
(org-load-modules-maybe)
(org-run-like-in-org-mode 'org-insert-link))
-(defun org-insert-all-links (&optional keep)
- "Insert all links in `org-stored-links'."
+(defun org-insert-all-links (arg &optional pre post)
+ "Insert all links in `org-stored-links'.
+When a universal prefix, do not delete the links from `org-stored-links'.
+When `ARG' is a number, insert the last N link(s).
+`PRE' and `POST' are optional arguments to define a string to
+prepend or to append."
(interactive "P")
- (let ((links (copy-sequence org-stored-links)) l)
- (while (setq l (if keep (pop links) (pop org-stored-links)))
- (insert "- ")
- (org-insert-link nil (car l) (or (cadr l) ""))
- (insert "\n"))))
+ (let ((org-keep-stored-link-after-insertion (equal arg '(4)))
+ (links (copy-sequence org-stored-links))
+ (pr (or pre "- "))
+ (po (or post "\n"))
+ (cnt 1) l)
+ (if (null org-stored-links)
+ (message "No link to insert")
+ (while (and (or (listp arg) (>= arg cnt))
+ (setq l (if (listp arg)
+ (pop links)
+ (pop org-stored-links))))
+ (setq cnt (1+ cnt))
+ (insert pr)
+ (org-insert-link nil (car l) (or (cadr l) ""))
+ (insert po)))))
+
+(defun org-insert-last-stored-link (arg)
+ "Insert the last link stored in `org-stored-links'."
+ (interactive "p")
+ (org-insert-all-links arg "" "\n"))
(defun org-link-fontify-links-to-this-file ()
"Fontify links to the current file in `org-stored-links'."
@@ -9946,73 +10336,73 @@ This command can be called in any mode to insert a link in Org-mode syntax."
(put-text-property 0 (length l) 'face 'font-lock-comment-face l))
(delq nil (append a b)))))
-(defvar org-link-links-in-this-file nil)
+(defvar org--links-history nil)
(defun org-insert-link (&optional complete-file link-location default-description)
"Insert a link. At the prompt, enter the link.
-Completion can be used to insert any of the link protocol prefixes like
-http or ftp in use.
+Completion can be used to insert any of the link protocol prefixes in use.
The history can be used to select a link previously stored with
`org-store-link'. When the empty string is entered (i.e. if you just
-press RET at the prompt), the link defaults to the most recently
-stored link. As SPC triggers completion in the minibuffer, you need to
-use M-SPC or C-q SPC to force the insertion of a space character.
+press `RET' at the prompt), the link defaults to the most recently
+stored link. As `SPC' triggers completion in the minibuffer, you need to
+use `M-SPC' or `C-q SPC' to force the insertion of a space character.
You will also be prompted for a description, and if one is given, it will
be displayed in the buffer instead of the link.
-If there is already a link at point, this command will allow you to edit link
-and description parts.
+If there is already a link at point, this command will allow you to edit
+link and description parts.
-With a \\[universal-argument] prefix, prompts for a file to link to. The file name can
-be selected using completion. The path to the file will be relative to the
+With a `\\[universal-argument]' prefix, prompts for a file to link to. The \
+file name can be
+selected using completion. The path to the file will be relative to the
current directory if the file is in the current directory or a subdirectory.
Otherwise, the link will be the absolute path as completed in the minibuffer
\(i.e. normally ~/path/to/file). You can configure this behavior using the
option `org-link-file-path-type'.
-With two \\[universal-argument] prefixes, enforce an absolute path even if the file is in
+With a `\\[universal-argument] \\[universal-argument]' prefix, enforce an \
+absolute path even if the file is in
the current directory or below.
-With three \\[universal-argument] prefixes, negate the meaning of
-`org-keep-stored-link-after-insertion'.
+A `\\[universal-argument] \\[universal-argument] \\[universal-argument]' \
+prefix negates `org-keep-stored-link-after-insertion'.
If `org-make-link-description-function' is non-nil, this function will be
called with the link target, and the result will be the default
link description.
-If the LINK-LOCATION parameter is non-nil, this value will be
-used as the link location instead of reading one interactively.
+If the LINK-LOCATION parameter is non-nil, this value will be used as
+the link location instead of reading one interactively.
-If the DEFAULT-DESCRIPTION parameter is non-nil, this value will
-be used as the default description."
+If the DEFAULT-DESCRIPTION parameter is non-nil, this value will be used
+as the default description."
(interactive "P")
(let* ((wcf (current-window-configuration))
(origbuf (current-buffer))
- (region (if (org-region-active-p)
- (buffer-substring (region-beginning) (region-end))))
+ (region (when (org-region-active-p)
+ (buffer-substring (region-beginning) (region-end))))
(remove (and region (list (region-beginning) (region-end))))
(desc region)
- tmphist ; byte-compile incorrectly complains about this
(link link-location)
(abbrevs org-link-abbrev-alist-local)
- entry file all-prefixes auto-desc)
+ entry all-prefixes auto-desc)
(cond
- (link-location) ; specified by arg, just use it.
+ (link-location) ; specified by arg, just use it.
((org-in-regexp org-bracket-link-regexp 1)
;; We do have a link at point, and we are going to edit it.
(setq remove (list (match-beginning 0) (match-end 0)))
- (setq desc (if (match-end 3) (org-match-string-no-properties 3)))
+ (setq desc (when (match-end 3) (match-string-no-properties 3)))
(setq link (read-string "Link: "
(org-link-unescape
- (org-match-string-no-properties 1)))))
+ (match-string-no-properties 1)))))
((or (org-in-regexp org-angle-link-re)
(org-in-regexp org-plain-link-re))
;; Convert to bracket link
(setq remove (list (match-beginning 0) (match-end 0))
link (read-string "Link: "
- (org-remove-angle-brackets (match-string 0)))))
+ (org-unbracket-string "<" ">" (match-string 0)))))
((member complete-file '((4) (16)))
;; Completing read for file names.
(setq link (org-file-complete-link complete-file)))
@@ -10035,92 +10425,91 @@ Use TAB to complete link prefixes, then RET for type-specific completion support
(unless (pos-visible-in-window-p (point-max))
(org-fit-window-to-buffer))
(and (window-live-p cw) (select-window cw)))
- ;; Fake a link history, containing the stored links.
- (setq tmphist (append (mapcar 'car org-stored-links)
- org-insert-link-history))
(setq all-prefixes (append (mapcar 'car abbrevs)
(mapcar 'car org-link-abbrev-alist)
- org-link-types))
+ (org-link-types)))
(unwind-protect
- (progn
+ ;; Fake a link history, containing the stored links.
+ (let ((org--links-history
+ (append (mapcar #'car org-stored-links)
+ org-insert-link-history)))
(setq link
(org-completing-read
"Link: "
(append
- (mapcar (lambda (x) (concat x ":"))
- all-prefixes)
- (mapcar 'car org-stored-links))
+ (mapcar (lambda (x) (concat x ":")) all-prefixes)
+ (mapcar #'car org-stored-links))
nil nil nil
- 'tmphist
+ 'org--links-history
(caar org-stored-links)))
- (if (not (string-match "\\S-" link))
- (user-error "No link selected"))
- (mapc (lambda(l)
- (when (equal link (cadr l)) (setq link (car l) auto-desc t)))
- org-stored-links)
- (if (or (member link all-prefixes)
- (and (equal ":" (substring link -1))
- (member (substring link 0 -1) all-prefixes)
- (setq link (substring link 0 -1))))
- (setq link (with-current-buffer origbuf
- (org-link-try-special-completion link)))))
+ (unless (org-string-nw-p link) (user-error "No link selected"))
+ (dolist (l org-stored-links)
+ (when (equal link (cadr l))
+ (setq link (car l))
+ (setq auto-desc t)))
+ (when (or (member link all-prefixes)
+ (and (equal ":" (substring link -1))
+ (member (substring link 0 -1) all-prefixes)
+ (setq link (substring link 0 -1))))
+ (setq link (with-current-buffer origbuf
+ (org-link-try-special-completion link)))))
(set-window-configuration wcf)
(kill-buffer "*Org Links*"))
(setq entry (assoc link org-stored-links))
(or entry (push link org-insert-link-history))
(setq desc (or desc (nth 1 entry)))))
- (if (funcall (if (equal complete-file '(64)) 'not 'identity)
- (not org-keep-stored-link-after-insertion))
- (setq org-stored-links (delq (assoc link org-stored-links)
- org-stored-links)))
+ (when (funcall (if (equal complete-file '(64)) 'not 'identity)
+ (not org-keep-stored-link-after-insertion))
+ (setq org-stored-links (delq (assoc link org-stored-links)
+ org-stored-links)))
- (if (and (string-match org-plain-link-re link)
- (not (string-match org-ts-regexp link)))
- ;; URL-like link, normalize the use of angular brackets.
- (setq link (org-remove-angle-brackets link)))
+ (when (and (string-match org-plain-link-re link)
+ (not (string-match org-ts-regexp link)))
+ ;; URL-like link, normalize the use of angular brackets.
+ (setq link (org-unbracket-string "<" ">" link)))
;; Check if we are linking to the current file with a search
;; option If yes, simplify the link by using only the search
;; option.
(when (and buffer-file-name
- (string-match "^file:\\(.+?\\)::\\(.+\\)" link))
- (let* ((path (match-string 1 link))
- (case-fold-search nil)
- (search (match-string 2 link)))
+ (let ((case-fold-search nil))
+ (string-match "\\`file:\\(.+?\\)::" link)))
+ (let ((path (match-string-no-properties 1 link))
+ (search (substring-no-properties link (match-end 0))))
(save-match-data
- (if (equal (file-truename buffer-file-name) (file-truename path))
- ;; We are linking to this same file, with a search option
- (setq link search)))))
+ (when (equal (file-truename buffer-file-name) (file-truename path))
+ ;; We are linking to this same file, with a search option
+ (setq link search)))))
;; Check if we can/should use a relative path. If yes, simplify the link
- (when (string-match "^\\(file:\\|docview:\\)\\(.*\\)" link)
- (let* ((type (match-string 1 link))
- (path (match-string 2 link))
- (origpath path)
- (case-fold-search nil))
- (cond
- ((or (eq org-link-file-path-type 'absolute)
- (equal complete-file '(16)))
- (setq path (abbreviate-file-name (expand-file-name path))))
- ((eq org-link-file-path-type 'noabbrev)
- (setq path (expand-file-name path)))
- ((eq org-link-file-path-type 'relative)
- (setq path (file-relative-name path)))
- (t
- (save-match-data
- (if (string-match (concat "^" (regexp-quote
- (expand-file-name
- (file-name-as-directory
- default-directory))))
- (expand-file-name path))
- ;; We are linking a file with relative path name.
- (setq path (substring (expand-file-name path)
- (match-end 0)))
- (setq path (abbreviate-file-name (expand-file-name path)))))))
- (setq link (concat type path))
- (if (equal desc origpath)
- (setq desc path))))
+ (let ((case-fold-search nil))
+ (when (string-match "\\`\\(file\\|docview\\):" link)
+ (let* ((type (match-string-no-properties 0 link))
+ (path (substring-no-properties link (match-end 0)))
+ (origpath path))
+ (cond
+ ((or (eq org-link-file-path-type 'absolute)
+ (equal complete-file '(16)))
+ (setq path (abbreviate-file-name (expand-file-name path))))
+ ((eq org-link-file-path-type 'noabbrev)
+ (setq path (expand-file-name path)))
+ ((eq org-link-file-path-type 'relative)
+ (setq path (file-relative-name path)))
+ (t
+ (save-match-data
+ (if (string-match (concat "^" (regexp-quote
+ (expand-file-name
+ (file-name-as-directory
+ default-directory))))
+ (expand-file-name path))
+ ;; We are linking a file with relative path name.
+ (setq path (substring (expand-file-name path)
+ (match-end 0)))
+ (setq path (abbreviate-file-name (expand-file-name path)))))))
+ (setq link (concat type path))
+ (when (equal desc origpath)
+ (setq desc path)))))
(if org-make-link-description-function
(setq desc
@@ -10135,49 +10524,36 @@ Use TAB to complete link prefixes, then RET for type-specific completion support
(read-string "Description: " desc)))))
(unless (string-match "\\S-" desc) (setq desc nil))
- (if remove (apply 'delete-region remove))
- (insert (org-make-link-string link desc))))
+ (when remove (apply 'delete-region remove))
+ (insert (org-make-link-string link desc))
+ ;; Redisplay so as the new link has proper invisible characters.
+ (sit-for 0)))
(defun org-link-try-special-completion (type)
"If there is completion support for link type TYPE, offer it."
- (let ((fun (intern (concat "org-" type "-complete-link"))))
+ (let ((fun (org-link-get-parameter type :complete)))
(if (functionp fun)
(funcall fun)
(read-string "Link (no completion support): " (concat type ":")))))
(defun org-file-complete-link (&optional arg)
"Create a file link using completion."
- (let (file link)
- (setq file (org-iread-file-name "File: "))
- (let ((pwd (file-name-as-directory (expand-file-name ".")))
- (pwd1 (file-name-as-directory (abbreviate-file-name
- (expand-file-name ".")))))
- (cond
- ((equal arg '(16))
- (setq link (concat
- "file:"
- (abbreviate-file-name (expand-file-name file)))))
- ((string-match (concat "^" (regexp-quote pwd1) "\\(.+\\)") file)
- (setq link (concat "file:" (match-string 1 file))))
- ((string-match (concat "^" (regexp-quote pwd) "\\(.+\\)")
- (expand-file-name file))
- (setq link (concat
- "file:" (match-string 1 (expand-file-name file)))))
- (t (setq link (concat "file:" file)))))
- link))
-
-(defun org-iread-file-name (&rest args)
- "Read-file-name using `ido-mode' speedup if available.
-ARGS are arguments that may be passed to `ido-read-file-name' or `read-file-name'.
-See `read-file-name' for a description of parameters."
- (org-without-partial-completion
- (if (and org-completion-use-ido
- (fboundp 'ido-read-file-name)
- (boundp 'ido-mode) ido-mode
- (listp (second args)))
- (let ((ido-enter-matching-directory nil))
- (apply 'ido-read-file-name args))
- (apply 'read-file-name args))))
+ (let ((file (read-file-name "File: "))
+ (pwd (file-name-as-directory (expand-file-name ".")))
+ (pwd1 (file-name-as-directory (abbreviate-file-name
+ (expand-file-name ".")))))
+ (cond ((equal arg '(16))
+ (concat "file:"
+ (abbreviate-file-name (expand-file-name file))))
+ ((string-match
+ (concat "^" (regexp-quote pwd1) "\\(.+\\)") file)
+ (concat "file:" (match-string 1 file)))
+ ((string-match
+ (concat "^" (regexp-quote pwd) "\\(.+\\)")
+ (expand-file-name file))
+ (concat "file:"
+ (match-string 1 (expand-file-name file))))
+ (t (concat "file:" file)))))
(defun org-completing-read (&rest args)
"Completing-read with SPACE being a normal character."
@@ -10186,58 +10562,9 @@ See `read-file-name' for a description of parameters."
(copy-keymap minibuffer-local-completion-map)))
(org-defkey minibuffer-local-completion-map " " 'self-insert-command)
(org-defkey minibuffer-local-completion-map "?" 'self-insert-command)
- (org-defkey minibuffer-local-completion-map (kbd "C-c !") 'org-time-stamp-inactive)
- (apply 'org-icompleting-read args)))
-
-(defun org-completing-read-no-i (&rest args)
- (let (org-completion-use-ido org-completion-use-iswitchb)
- (apply 'org-completing-read args)))
-
-(defun org-iswitchb-completing-read (prompt choices &rest args)
- "Use iswitch as a completing-read replacement to choose from choices.
-PROMPT is a string to prompt with. CHOICES is a list of strings to choose
-from."
- (let* ((iswitchb-use-virtual-buffers nil)
- (iswitchb-make-buflist-hook
- (lambda ()
- (setq iswitchb-temp-buflist choices))))
- (iswitchb-read-buffer prompt)))
-
-(defun org-icompleting-read (&rest args)
- "Completing-read using `ido-mode' or `iswitchb' speedups if available."
- (org-without-partial-completion
- (if (and org-completion-use-ido
- (fboundp 'ido-completing-read)
- (boundp 'ido-mode) ido-mode
- (listp (second args)))
- (let ((ido-enter-matching-directory nil))
- (apply 'ido-completing-read (concat (car args))
- (if (consp (car (nth 1 args)))
- (mapcar 'car (nth 1 args))
- (nth 1 args))
- (cddr args)))
- (if (and org-completion-use-iswitchb
- (boundp 'iswitchb-mode) iswitchb-mode
- (listp (second args)))
- (apply 'org-iswitchb-completing-read (concat (car args))
- (if (consp (car (nth 1 args)))
- (mapcar 'car (nth 1 args))
- (nth 1 args))
- (cddr args))
- (apply 'completing-read args)))))
-
-(defun org-extract-attributes (s)
- "Extract the attributes cookie from a string and set as text property."
- (let (a attr (start 0) key value)
- (save-match-data
- (when (string-match "{{\\([^}]+\\)}}$" s)
- (setq a (match-string 1 s) s (substring s 0 (match-beginning 0)))
- (while (string-match "\\([a-zA-Z]+\\)=\"\\([^\"]*\\)\"" a start)
- (setq key (match-string 1 a) value (match-string 2 a)
- start (match-end 0)
- attr (plist-put attr (intern key) value))))
- (org-add-props s nil 'org-attr attr))
- s))
+ (org-defkey minibuffer-local-completion-map (kbd "C-c !")
+ 'org-time-stamp-inactive)
+ (apply #'completing-read args)))
;;; Opening/following a link
@@ -10257,8 +10584,8 @@ handle this as a special case.
When the function does handle the link, it must return a non-nil value.
If it decides that it is not responsible for this link, it must return
-nil to indicate that that Org-mode can continue with other options
-like exact and fuzzy text search.")
+nil to indicate that that Org can continue with other options like
+exact and fuzzy text search.")
(defun org-next-link (&optional search-backward)
"Move forward to the next link.
@@ -10270,7 +10597,7 @@ If the link is in hidden text, expose it."
(setq org-link-search-failed nil)
(let* ((pos (point))
(ct (org-context))
- (a (assoc :link ct))
+ (a (assq :link ct))
(srch-fun (if search-backward 're-search-backward 're-search-forward)))
(cond (a (goto-char (nth (if search-backward 1 2) a)))
((looking-at org-any-link-re)
@@ -10279,7 +10606,7 @@ If the link is in hidden text, expose it."
(if (funcall srch-fun org-any-link-re nil t)
(progn
(goto-char (match-beginning 0))
- (if (outline-invisible-p) (org-show-context)))
+ (when (org-invisible-p) (org-show-context)))
(goto-char pos)
(setq org-link-search-failed t)
(message "No further link found"))))
@@ -10292,14 +10619,9 @@ If the link is in hidden text, expose it."
(defun org-translate-link (s)
"Translate a link string if a translation function has been defined."
- (if (and org-link-translation-function
- (fboundp org-link-translation-function)
- (string-match "\\([a-zA-Z0-9]+\\):\\(.*\\)" s))
- (progn
- (setq s (funcall org-link-translation-function
- (match-string 1 s) (match-string 2 s)))
- (concat (car s) ":" (cdr s)))
- s))
+ (with-temp-buffer
+ (insert (org-trim s))
+ (org-trim (org-element-interpret-data (org-element-context)))))
(defun org-translate-link-from-planner (type path)
"Translate a link from Emacs Planner syntax so that Org can follow it.
@@ -10319,7 +10641,7 @@ This is still an experimental function, your mileage may vary."
;; A typical message link. Planner has the id after the final slash,
;; we separate it with a hash mark
(setq path (concat (match-string 1 path) "#"
- (org-remove-angle-brackets (match-string 2 path))))))
+ (org-unbracket-string "<" ">" (match-string 2 path))))))
(cons type path))
(defun org-find-file-at-mouse (ev)
@@ -10333,28 +10655,32 @@ This is still an experimental function, your mileage may vary."
See the docstring of `org-open-file' for details."
(interactive "e")
(mouse-set-point ev)
- (if (eq major-mode 'org-agenda-mode)
- (org-agenda-copy-local-variable 'org-link-abbrev-alist-local))
+ (when (eq major-mode 'org-agenda-mode)
+ (org-agenda-copy-local-variable 'org-link-abbrev-alist-local))
(org-open-at-point))
(defvar org-window-config-before-follow-link nil
"The window configuration before following a link.
This is saved in case the need arises to restore it.")
-(defvar org-open-link-marker (make-marker)
- "Marker pointing to the location where `org-open-at-point' was called.")
-
;;;###autoload
(defun org-open-at-point-global ()
- "Follow a link like Org-mode does.
-This command can be called in any mode to follow a link that has
-Org-mode syntax."
+ "Follow a link or time-stamp like Org mode does.
+This command can be called in any mode to follow an external link
+or a time-stamp that has Org mode syntax. Its behavior is
+undefined when called on internal links (e.g., fuzzy links).
+Raise an error when there is nothing to follow. "
(interactive)
- (org-run-like-in-org-mode 'org-open-at-point))
+ (cond ((org-in-regexp org-any-link-re)
+ (org-open-link-from-string (match-string-no-properties 0)))
+ ((or (org-in-regexp org-ts-regexp-both nil t)
+ (org-in-regexp org-tsr-regexp-both nil t))
+ (org-follow-timestamp-link))
+ (t (user-error "No link found"))))
;;;###autoload
(defun org-open-link-from-string (s &optional arg reference-buffer)
- "Open a link in the string S, as if it was in Org-mode."
+ "Open a link in the string S, as if it was in Org mode."
(interactive "sLink: \nP")
(let ((reference-buffer (or reference-buffer (current-buffer))))
(with-temp-buffer
@@ -10375,267 +10701,240 @@ Functions in this hook must return t if they identify and follow
a link at point. If they don't find anything interesting at point,
they must return nil.")
-(defvar org-link-search-inhibit-query nil) ;; dynamically scoped
-(defvar clean-buffer-list-kill-buffer-names) ; Defined in midnight.el
+(defvar org-link-search-inhibit-query nil)
+(defvar clean-buffer-list-kill-buffer-names) ;Defined in midnight.el
+(defun org--open-doi-link (path)
+ "Open a \"doi\" type link.
+PATH is a the path to search for, as a string."
+ (browse-url (url-encode-url (concat org-doi-server-url path))))
+
+(defun org--open-elisp-link (path)
+ "Open a \"elisp\" type link.
+PATH is the sexp to evaluate, as a string."
+ (let ((cmd path))
+ (if (or (and (org-string-nw-p
+ org-confirm-elisp-link-not-regexp)
+ (string-match-p org-confirm-elisp-link-not-regexp cmd))
+ (not org-confirm-elisp-link-function)
+ (funcall org-confirm-elisp-link-function
+ (format "Execute \"%s\" as elisp? "
+ (org-add-props cmd nil 'face 'org-warning))))
+ (message "%s => %s" cmd
+ (if (eq (string-to-char cmd) ?\()
+ (eval (read cmd))
+ (call-interactively (read cmd))))
+ (user-error "Abort"))))
+
+(defun org--open-help-link (path)
+ "Open a \"help\" type link.
+PATH is a symbol name, as a string."
+ (pcase (intern path)
+ ((and (pred fboundp) variable) (describe-function variable))
+ ((and (pred boundp) function) (describe-variable function))
+ (name (user-error "Unknown function or variable: %s" name))))
+
+(defun org--open-shell-link (path)
+ "Open a \"shell\" type link.
+PATH is the command to execute, as a string."
+ (let ((buf (generate-new-buffer "*Org Shell Output*"))
+ (cmd path))
+ (if (or (and (org-string-nw-p
+ org-confirm-shell-link-not-regexp)
+ (string-match
+ org-confirm-shell-link-not-regexp cmd))
+ (not org-confirm-shell-link-function)
+ (funcall org-confirm-shell-link-function
+ (format "Execute \"%s\" in shell? "
+ (org-add-props cmd nil
+ 'face 'org-warning))))
+ (progn
+ (message "Executing %s" cmd)
+ (shell-command cmd buf)
+ (when (featurep 'midnight)
+ (setq clean-buffer-list-kill-buffer-names
+ (cons (buffer-name buf)
+ clean-buffer-list-kill-buffer-names))))
+ (user-error "Abort"))))
+
(defun org-open-at-point (&optional arg reference-buffer)
- "Open link at or after point.
-If there is no link at point, this function will search forward up to
-the end of the current line.
-Normally, files will be opened by an appropriate application. If the
-optional prefix argument ARG is non-nil, Emacs will visit the file.
-With a double prefix argument, try to open outside of Emacs, in the
-application the system uses for this file type."
- (interactive "P")
- ;; if in a code block, then open the block's results
- (unless (call-interactively #'org-babel-open-src-block-result)
- (org-load-modules-maybe)
- (move-marker org-open-link-marker (point))
- (setq org-window-config-before-follow-link (current-window-configuration))
- (org-remove-occur-highlights nil nil t)
- (cond
- ((and (org-at-heading-p)
- (not (org-at-timestamp-p t))
- (not (org-in-regexp
- (concat org-plain-link-re "\\|"
- org-bracket-link-regexp "\\|"
- org-angle-link-re "\\|"
- "[ \t]:[^ \t\n]+:[ \t]*$")))
- (not (get-text-property (point) 'org-linked-text)))
- (or (let* ((lkall (org-offer-links-in-entry (current-buffer) (point) arg))
- (lk0 (car lkall))
- (lk (if (stringp lk0) (list lk0) lk0))
- (lkend (cdr lkall)))
- (mapcar (lambda(l)
- (search-forward l nil lkend)
- (goto-char (match-beginning 0))
- (org-open-at-point))
- lk))
- (progn (require 'org-attach) (org-attach-reveal 'if-exists))))
- ((run-hook-with-args-until-success 'org-open-at-point-functions))
- ((and (org-at-timestamp-p t)
- (not (org-in-regexp org-bracket-link-regexp)))
- (org-follow-timestamp-link))
- ((and (or (org-footnote-at-reference-p) (org-footnote-at-definition-p))
- (not (org-in-regexp org-any-link-re)))
- (org-footnote-action))
- (t
- (let (type path link line search (pos (point)))
- (catch 'match
- (save-excursion
- (or (org-in-regexp org-plain-link-re)
- (skip-chars-forward "^]\n\r"))
- (when (org-in-regexp org-bracket-link-regexp 1)
- (setq link (org-extract-attributes
- (org-link-unescape (org-match-string-no-properties 1))))
- (while (string-match " *\n *" link)
- (setq link (replace-match " " t t link)))
- (setq link (org-link-expand-abbrev link))
- (cond
- ((or (file-name-absolute-p link)
- (string-match "^\\.\\.?/" link))
- (setq type "file" path link))
- ((string-match org-link-re-with-space3 link)
- (setq type (match-string 1 link) path (match-string 2 link)))
- ((string-match "^help:+\\(.+\\)" link)
- (setq type "help" path (match-string 1 link)))
- (t (setq type "thisfile" path link)))
- (throw 'match t)))
-
- (when (get-text-property (point) 'org-linked-text)
- (setq type "thisfile"
- pos (if (get-text-property (1+ (point)) 'org-linked-text)
- (1+ (point)) (point))
- path (buffer-substring
- (or (previous-single-property-change pos 'org-linked-text)
- (point-min))
- (or (next-single-property-change pos 'org-linked-text)
- (point-max)))
- ;; Ensure we will search for a <<>> link, not
- ;; a simple reference like <>
- path (concat "<" path))
- (throw 'match t))
+ "Open link, timestamp, footnote or tags at point.
- (save-excursion
- (when (or (org-in-regexp org-angle-link-re)
- (let ((match (org-in-regexp org-plain-link-re)))
- ;; Check a plain link is not within a bracket link
- (and match
- (save-excursion
- (save-match-data
- (progn
- (goto-char (car match))
- (not (org-in-regexp org-bracket-link-regexp)))))))
- (let ((line_ending (save-excursion (end-of-line) (point))))
- ;; We are in a line before a plain or bracket link
- (or (re-search-forward org-plain-link-re line_ending t)
- (re-search-forward org-bracket-link-regexp line_ending t))))
- (setq type (match-string 1)
- path (org-link-unescape (match-string 2)))
- (throw 'match t)))
- (save-excursion
- (when (org-in-regexp (org-re "\\(:[[:alnum:]_@#%:]+\\):[ \t]*$"))
- (setq type "tags"
- path (match-string 1))
- (while (string-match ":" path)
- (setq path (replace-match "+" t t path)))
- (throw 'match t)))
- (when (org-in-regexp "<\\([^><\n]+\\)>")
- (setq type "tree-match"
- path (match-string 1))
- (throw 'match t)))
- (unless path
- (user-error "No link found"))
+When point is on a link, follow it. Normally, files will be
+opened by an appropriate application. If the optional prefix
+argument ARG is non-nil, Emacs will visit the file. With
+a double prefix argument, try to open outside of Emacs, in the
+application the system uses for this file type.
- ;; switch back to reference buffer
- ;; needed when if called in a temporary buffer through
- ;; org-open-link-from-string
- (with-current-buffer (or reference-buffer (current-buffer))
+When point is on a timestamp, open the agenda at the day
+specified.
- ;; Remove any trailing spaces in path
- (if (string-match " +\\'" path)
- (setq path (replace-match "" t t path)))
- (if (and org-link-translation-function
- (fboundp org-link-translation-function))
- ;; Check if we need to translate the link
- (let ((tmp (funcall org-link-translation-function type path)))
- (setq type (car tmp) path (cdr tmp))))
+When point is a footnote definition, move to the first reference
+found. If it is on a reference, move to the associated
+definition.
- (cond
+When point is on a headline, display a list of every link in the
+entry, so it is possible to pick one, or all, of them. If point
+is on a tag, call `org-tags-view' instead.
- ((assoc type org-link-protocols)
- (funcall (nth 1 (assoc type org-link-protocols)) path))
-
- ((equal type "help")
- (let ((f-or-v (intern path)))
- (cond ((fboundp f-or-v)
- (describe-function f-or-v))
- ((boundp f-or-v)
- (describe-variable f-or-v))
- (t (error "Not a known function or variable")))))
-
- ((equal type "mailto")
- (let ((cmd (car org-link-mailto-program))
- (args (cdr org-link-mailto-program)) args1
- (address path) (subject "") a)
- (if (string-match "\\(.*\\)::\\(.*\\)" path)
- (setq address (match-string 1 path)
- subject (org-link-escape (match-string 2 path))))
- (while args
- (cond
- ((not (stringp (car args))) (push (pop args) args1))
- (t (setq a (pop args))
- (if (string-match "%a" a)
- (setq a (replace-match address t t a)))
- (if (string-match "%s" a)
- (setq a (replace-match subject t t a)))
- (push a args1))))
- (apply cmd (nreverse args1))))
-
- ((member type '("http" "https" "ftp" "news"))
- (browse-url
- (concat type ":"
- (if (org-string-match-p
- (concat "[[:nonascii:]"
- org-link-escape-chars-browser "]")
- path)
- (org-link-escape path org-link-escape-chars-browser)
- path))))
-
- ((string= type "doi")
- (browse-url
- (concat org-doi-server-url
- (if (org-string-match-p
- (concat "[[:nonascii:]"
- org-link-escape-chars-browser "]")
- path)
- (org-link-escape path org-link-escape-chars-browser)
- path))))
-
- ((member type '("message"))
- (browse-url (concat type ":" path)))
-
- ((string= type "tags")
- (org-tags-view arg path))
-
- ((string= type "tree-match")
- (org-occur (concat "\\[" (regexp-quote path) "\\]")))
-
- ((string= type "file")
- (if (string-match "::\\([0-9]+\\)\\'" path)
- (setq line (string-to-number (match-string 1 path))
- path (substring path 0 (match-beginning 0)))
- (if (string-match "::\\(.+\\)\\'" path)
- (setq search (match-string 1 path)
- path (substring path 0 (match-beginning 0)))))
- (if (string-match "[*?{]" (file-name-nondirectory path))
- (dired path)
- (org-open-file path arg line search)))
-
- ((string= type "shell")
- (let ((buf (generate-new-buffer "*Org Shell Output"))
- (cmd path))
- (if (or (and (not (string= org-confirm-shell-link-not-regexp ""))
- (string-match org-confirm-shell-link-not-regexp cmd))
- (not org-confirm-shell-link-function)
- (funcall org-confirm-shell-link-function
- (format "Execute \"%s\" in shell? "
- (org-add-props cmd nil
- 'face 'org-warning))))
- (progn
- (message "Executing %s" cmd)
- (shell-command cmd buf)
- (if (featurep 'midnight)
- (setq clean-buffer-list-kill-buffer-names
- (cons buf clean-buffer-list-kill-buffer-names))))
- (error "Abort"))))
-
- ((string= type "elisp")
- (let ((cmd path))
- (if (or (and (not (string= org-confirm-elisp-link-not-regexp ""))
- (string-match org-confirm-elisp-link-not-regexp cmd))
- (not org-confirm-elisp-link-function)
- (funcall org-confirm-elisp-link-function
- (format "Execute \"%s\" as elisp? "
- (org-add-props cmd nil
- 'face 'org-warning))))
- (message "%s => %s" cmd
- (if (equal (string-to-char cmd) ?\()
- (eval (read cmd))
- (call-interactively (read cmd))))
- (error "Abort"))))
-
- ((and (string= type "thisfile")
- (or (run-hook-with-args-until-success
- 'org-open-link-functions path)
- (and link
- (string-match "^id:" link)
- (or (featurep 'org-id) (require 'org-id))
- (progn
- (funcall (nth 1 (assoc "id" org-link-protocols))
- (substring path 3))
- t)))))
-
- ((string= type "thisfile")
- (if arg
- (switch-to-buffer-other-window
- (org-get-buffer-for-internal-link (current-buffer)))
- (org-mark-ring-push))
- (let ((cmd `(org-link-search
- ,path
- ,(cond ((equal arg '(4)) ''occur)
- ((equal arg '(16)) ''org-occur))
- ,pos)))
- (condition-case nil (let ((org-link-search-inhibit-query t))
- (eval cmd))
- (error (progn (widen) (eval cmd))))))
-
- (t (browse-url-at-point)))))))
- (move-marker org-open-link-marker nil)
- (run-hook-with-args 'org-follow-link-hook)))
+When optional argument REFERENCE-BUFFER is non-nil, it should
+specify a buffer from where the link search should happen. This
+is used internally by `org-open-link-from-string'.
-(defsubst org-uniquify (list)
- "Non-destructively remove duplicate elements from LIST."
- (let ((res (copy-sequence list))) (delete-dups res)))
+On top of syntactically correct links, this function will open
+the link at point in comments or comment blocks and the first
+link in a property drawer line."
+ (interactive "P")
+ ;; On a code block, open block's results.
+ (unless (call-interactively 'org-babel-open-src-block-result)
+ (org-load-modules-maybe)
+ (setq org-window-config-before-follow-link (current-window-configuration))
+ (org-remove-occur-highlights nil nil t)
+ (unless (run-hook-with-args-until-success 'org-open-at-point-functions)
+ (let* ((context
+ ;; Only consider supported types, even if they are not
+ ;; the closest one.
+ (org-element-lineage
+ (org-element-context)
+ '(clock comment comment-block footnote-definition
+ footnote-reference headline inlinetask keyword link
+ node-property timestamp)
+ t))
+ (type (org-element-type context))
+ (value (org-element-property :value context)))
+ (cond
+ ((not context) (user-error "No link found"))
+ ;; Exception: open timestamps and links in properties
+ ;; drawers, keywords and comments.
+ ((memq type '(comment comment-block keyword node-property))
+ (call-interactively #'org-open-at-point-global))
+ ;; On a headline or an inlinetask, but not on a timestamp,
+ ;; a link, a footnote reference or on tags.
+ ((and (memq type '(headline inlinetask))
+ ;; Not on tags.
+ (let ((case-fold-search nil))
+ (save-excursion
+ (beginning-of-line)
+ (looking-at org-complex-heading-regexp))
+ (or (not (match-beginning 5))
+ (< (point) (match-beginning 5)))))
+ (let* ((data (org-offer-links-in-entry (current-buffer) (point) arg))
+ (links (car data))
+ (links-end (cdr data)))
+ (if links
+ (dolist (link (if (stringp links) (list links) links))
+ (search-forward link nil links-end)
+ (goto-char (match-beginning 0))
+ (org-open-at-point))
+ (require 'org-attach)
+ (org-attach-reveal 'if-exists))))
+ ;; On a clock line, make sure point is on the timestamp
+ ;; before opening it.
+ ((and (eq type 'clock)
+ value
+ (>= (point) (org-element-property :begin value))
+ (<= (point) (org-element-property :end value)))
+ (org-follow-timestamp-link))
+ ;; Do nothing on white spaces after an object.
+ ((>= (point)
+ (save-excursion
+ (goto-char (org-element-property :end context))
+ (skip-chars-backward " \t")
+ (point)))
+ (user-error "No link found"))
+ ((eq type 'timestamp) (org-follow-timestamp-link))
+ ;; On tags within a headline or an inlinetask.
+ ((and (memq type '(headline inlinetask))
+ (let ((case-fold-search nil))
+ (save-excursion (beginning-of-line)
+ (looking-at org-complex-heading-regexp))
+ (and (match-beginning 5)
+ (>= (point) (match-beginning 5)))))
+ (org-tags-view arg (substring (match-string 5) 0 -1)))
+ ((eq type 'link)
+ ;; When link is located within the description of another
+ ;; link (e.g., an inline image), always open the parent
+ ;; link.
+ (let* ((link (let ((up (org-element-property :parent context)))
+ (if (eq (org-element-type up) 'link) up context)))
+ (type (org-element-property :type link))
+ (path (org-link-unescape (org-element-property :path link))))
+ ;; Switch back to REFERENCE-BUFFER needed when called in
+ ;; a temporary buffer through `org-open-link-from-string'.
+ (with-current-buffer (or reference-buffer (current-buffer))
+ (cond
+ ((equal type "file")
+ (if (string-match "[*?{]" (file-name-nondirectory path))
+ (dired path)
+ ;; Look into `org-link-parameters' in order to find
+ ;; a DEDICATED-FUNCTION to open file. The function
+ ;; will be applied on raw link instead of parsed
+ ;; link due to the limitation in `org-add-link-type'
+ ;; ("open" function called with a single argument).
+ ;; If no such function is found, fallback to
+ ;; `org-open-file'.
+ (let* ((option (org-element-property :search-option link))
+ (app (org-element-property :application link))
+ (dedicated-function
+ (org-link-get-parameter
+ (if app (concat type "+" app) type)
+ :follow)))
+ (if dedicated-function
+ (funcall dedicated-function
+ (concat path
+ (and option (concat "::" option))))
+ (apply #'org-open-file
+ path
+ (cond (arg)
+ ((equal app "emacs") 'emacs)
+ ((equal app "sys") 'system))
+ (cond ((not option) nil)
+ ((string-match-p "\\`[0-9]+\\'" option)
+ (list (string-to-number option)))
+ (t (list nil
+ (org-link-unescape option)))))))))
+ ((functionp (org-link-get-parameter type :follow))
+ (funcall (org-link-get-parameter type :follow) path))
+ ((member type '("coderef" "custom-id" "fuzzy" "radio"))
+ (unless (run-hook-with-args-until-success
+ 'org-open-link-functions path)
+ (if (not arg) (org-mark-ring-push)
+ (switch-to-buffer-other-window
+ (org-get-buffer-for-internal-link (current-buffer))))
+ (let ((destination
+ (org-with-wide-buffer
+ (if (equal type "radio")
+ (org-search-radio-target
+ (org-element-property :path link))
+ (org-link-search
+ (if (member type '("custom-id" "coderef"))
+ (org-element-property :raw-link link)
+ path)
+ ;; Prevent fuzzy links from matching
+ ;; themselves.
+ (and (equal type "fuzzy")
+ (+ 2 (org-element-property :begin link)))))
+ (point))))
+ (unless (and (<= (point-min) destination)
+ (>= (point-max) destination))
+ (widen))
+ (goto-char destination))))
+ (t (browse-url-at-point))))))
+ ;; On a footnote reference or at a footnote definition's label.
+ ((or (eq type 'footnote-reference)
+ (and (eq type 'footnote-definition)
+ (save-excursion
+ ;; Do not validate action when point is on the
+ ;; spaces right after the footnote label, in
+ ;; order to be on par with behaviour on links.
+ (skip-chars-forward " \t")
+ (let ((begin
+ (org-element-property :contents-begin context)))
+ (if begin (< (point) begin)
+ (= (org-element-property :post-affiliated context)
+ (line-beginning-position)))))))
+ (org-footnote-action))
+ (t (user-error "No link found")))))
+ (run-hook-with-args 'org-follow-link-hook)))
(defun org-offer-links-in-entry (buffer marker &optional nth zero)
"Offer links in the current entry and return the selected link.
@@ -10644,65 +10943,57 @@ If NTH is an integer, return the NTH link found.
If ZERO is a string, check also this string for a link, and if
there is one, return it."
(with-current-buffer buffer
- (save-excursion
- (save-restriction
- (widen)
- (goto-char marker)
- (let ((re (concat "\\(" org-bracket-link-regexp "\\)\\|"
- "\\(" org-angle-link-re "\\)\\|"
- "\\(" org-plain-link-re "\\)"))
- (cnt ?0)
- (in-emacs (if (integerp nth) nil nth))
- have-zero end links link c)
- (when (and (stringp zero) (string-match org-bracket-link-regexp zero))
- (push (match-string 0 zero) links)
- (setq cnt (1- cnt) have-zero t))
- (save-excursion
- (org-back-to-heading t)
- (setq end (save-excursion (outline-next-heading) (point)))
- (while (re-search-forward re end t)
- (push (match-string 0) links))
- (setq links (org-uniquify (reverse links))))
- (cond
- ((null links)
- (message "No links"))
- ((equal (length links) 1)
- (setq link (car links)))
- ((and (integerp nth) (>= (length links) (if have-zero (1+ nth) nth)))
- (setq link (nth (if have-zero nth (1- nth)) links)))
- (t ; we have to select a link
- (save-excursion
- (save-window-excursion
- (delete-other-windows)
- (with-output-to-temp-buffer "*Select Link*"
- (mapc (lambda (l)
- (if (not (string-match org-bracket-link-regexp l))
- (princ (format "[%c] %s\n" (incf cnt)
- (org-remove-angle-brackets l)))
- (if (match-end 3)
- (princ (format "[%c] %s (%s)\n" (incf cnt)
- (match-string 3 l) (match-string 1 l)))
- (princ (format "[%c] %s\n" (incf cnt)
- (match-string 1 l))))))
- links))
- (org-fit-window-to-buffer (get-buffer-window "*Select Link*"))
- (message "Select link to open, RET to open all:")
- (setq c (read-char-exclusive))
- (and (get-buffer "*Select Link*") (kill-buffer "*Select Link*"))))
- (when (equal c ?q) (error "Abort"))
- (if (equal c ?\C-m)
- (setq link links)
- (setq nth (- c ?0))
- (if have-zero (setq nth (1+ nth)))
- (unless (and (integerp nth) (>= (length links) nth))
- (user-error "Invalid link selection"))
- (setq link (nth (1- nth) links)))))
- (cons link end))))))
-
-;; Add special file links that specify the way of opening
-
-(org-add-link-type "file+sys" 'org-open-file-with-system)
-(org-add-link-type "file+emacs" 'org-open-file-with-emacs)
+ (org-with-wide-buffer
+ (goto-char marker)
+ (let ((cnt ?0)
+ have-zero end links link c)
+ (when (and (stringp zero) (string-match org-bracket-link-regexp zero))
+ (push (match-string 0 zero) links)
+ (setq cnt (1- cnt) have-zero t))
+ (save-excursion
+ (org-back-to-heading t)
+ (setq end (save-excursion (outline-next-heading) (point)))
+ (while (re-search-forward org-any-link-re end t)
+ (push (match-string 0) links))
+ (setq links (org-uniquify (reverse links))))
+ (cond
+ ((null links)
+ (message "No links"))
+ ((equal (length links) 1)
+ (setq link (car links)))
+ ((and (integerp nth) (>= (length links) (if have-zero (1+ nth) nth)))
+ (setq link (nth (if have-zero nth (1- nth)) links)))
+ (t ; we have to select a link
+ (save-excursion
+ (save-window-excursion
+ (delete-other-windows)
+ (with-output-to-temp-buffer "*Select Link*"
+ (dolist (l links)
+ (cond
+ ((not (string-match org-bracket-link-regexp l))
+ (princ (format "[%c] %s\n" (cl-incf cnt)
+ (org-unbracket-string "<" ">" l))))
+ ((match-end 3)
+ (princ (format "[%c] %s (%s)\n" (cl-incf cnt)
+ (match-string 3 l) (match-string 1 l))))
+ (t (princ (format "[%c] %s\n" (cl-incf cnt)
+ (match-string 1 l)))))))
+ (org-fit-window-to-buffer (get-buffer-window "*Select Link*"))
+ (message "Select link to open, RET to open all:")
+ (setq c (read-char-exclusive))
+ (and (get-buffer "*Select Link*") (kill-buffer "*Select Link*"))))
+ (when (equal c ?q) (user-error "Abort"))
+ (if (equal c ?\C-m)
+ (setq link links)
+ (setq nth (- c ?0))
+ (when have-zero (setq nth (1+ nth)))
+ (unless (and (integerp nth) (>= (length links) nth))
+ (user-error "Invalid link selection"))
+ (setq link (nth (1- nth) links)))))
+ (cons link end)))))
+
+;; TODO: These functions are deprecated since `org-open-at-point'
+;; hard-codes behaviour for "file+emacs" and "file+sys" types.
(defun org-open-file-with-system (path)
"Open file at PATH using the system way of opening it."
(org-open-file path 'system))
@@ -10732,8 +11023,8 @@ which see.
A function in this hook may also use `setq' to set the variable
`description' to provide a suggestion for the descriptive text to
-be used for this link when it gets inserted into an Org-mode
-buffer with \\[org-insert-link].")
+be used for this link when it gets inserted into an Org buffer
+with \\[org-insert-link].")
(defvar org-execute-file-search-functions nil
"List of functions to execute a file search triggered by a link.
@@ -10757,179 +11048,201 @@ the window configuration before `org-open-at-point' was called using:
(set-window-configuration org-window-config-before-follow-link)")
-(defun org-link-search (s &optional type avoid-pos stealth)
- "Search for a link search option.
-If S is surrounded by forward slashes, it is interpreted as a
-regular expression. In org-mode files, this will create an `org-occur'
-sparse tree. In ordinary files, `occur' will be used to list matches.
-If the current buffer is in `dired-mode', grep will be used to search
-in all files. If AVOID-POS is given, ignore matches near that position.
+(defun org-search-radio-target (target)
+ "Search a radio target matching TARGET in current buffer.
+White spaces are not significant."
+ (let ((re (format "<<<%s>>>"
+ (mapconcat #'regexp-quote
+ (org-split-string target "[ \t\n]+")
+ "[ \t]+\\(?:\n[ \t]*\\)?")))
+ (origin (point)))
+ (goto-char (point-min))
+ (catch :radio-match
+ (while (re-search-forward re nil t)
+ (backward-char)
+ (let ((object (org-element-context)))
+ (when (eq (org-element-type object) 'radio-target)
+ (goto-char (org-element-property :begin object))
+ (org-show-context 'link-search)
+ (throw :radio-match nil))))
+ (goto-char origin)
+ (user-error "No match for radio target: %s" target))))
+
+(defun org-link-search (s &optional avoid-pos stealth)
+ "Search for a search string S.
+
+If S starts with \"#\", it triggers a custom ID search.
+
+If S is enclosed within parenthesis, it initiates a coderef
+search.
+
+If S is surrounded by forward slashes, it is interpreted as
+a regular expression. In Org mode files, this will create an
+`org-occur' sparse tree. In ordinary files, `occur' will be used
+to list matches. If the current buffer is in `dired-mode', grep
+will be used to search in all files.
+
+When AVOID-POS is given, ignore matches near that position.
When optional argument STEALTH is non-nil, do not modify
-visibility around point, thus ignoring
-`org-show-hierarchy-above', `org-show-following-heading' and
-`org-show-siblings' variables."
- (let ((case-fold-search t)
- (s0 (mapconcat 'identity (org-split-string s "[ \t\r\n]+") " "))
- (markers (concat "\\(?:" (mapconcat (lambda (x) (regexp-quote (car x)))
- (append '(("") (" ") ("\t") ("\n"))
- org-emphasis-alist)
- "\\|") "\\)"))
- (pos (point))
- (pre nil) (post nil)
- words re0 re1 re2 re3 re4_ re4 re5 re2a re2a_ reall)
+visibility around point, thus ignoring `org-show-context-detail'
+variable.
+
+Search is case-insensitive and ignores white spaces. Return type
+of matched result, which is either `dedicated' or `fuzzy'."
+ (unless (org-string-nw-p s) (error "Invalid search string \"%s\"" s))
+ (let* ((case-fold-search t)
+ (origin (point))
+ (normalized (replace-regexp-in-string "\n[ \t]*" " " s))
+ (starred (eq (string-to-char normalized) ?*))
+ (words (split-string (if starred (substring s 1) s)))
+ (s-multi-re (mapconcat #'regexp-quote words "\\(?:[ \t\n]+\\)"))
+ (s-single-re (mapconcat #'regexp-quote words "[ \t]+"))
+ type)
(cond
- ;; First check if there are any special search functions
+ ;; Check if there are any special search functions.
((run-hook-with-args-until-success 'org-execute-file-search-functions s))
- ;; Now try the builtin stuff
- ((and (equal (string-to-char s0) ?#)
- (> (length s0) 1)
- (save-excursion
- (goto-char (point-min))
- (and
- (re-search-forward
- (concat "^[ \t]*:CUSTOM_ID:[ \t]+"
- (regexp-quote (substring s0 1)) "[ \t]*$") nil t)
- (setq type 'dedicated
- pos (match-beginning 0))))
- ;; There is an exact target for this
- (goto-char pos)
- (org-back-to-heading t)))
- ((save-excursion
+ ((eq (string-to-char s) ?#)
+ ;; Look for a custom ID S if S starts with "#".
+ (let* ((id (substring normalized 1))
+ (match (org-find-property "CUSTOM_ID" id)))
+ (if match (progn (goto-char match) (setf type 'dedicated))
+ (error "No match for custom ID: %s" id))))
+ ((string-match "\\`(\\(.*\\))\\'" normalized)
+ ;; Look for coderef targets if S is enclosed within parenthesis.
+ (let ((coderef (match-string-no-properties 1 normalized))
+ (re (substring s-single-re 1 -1)))
(goto-char (point-min))
- (and
- (re-search-forward
- (concat "<<" (regexp-quote s0) ">>") nil t)
- (setq type 'dedicated
- pos (match-beginning 0))))
- ;; There is an exact target for this
- (goto-char pos))
- ((save-excursion
- (goto-char (point-min))
- (and
- (re-search-forward
- (format "^[ \t]*#\\+NAME: %s" (regexp-quote s0)) nil t)
- (setq type 'dedicated pos (match-beginning 0))))
- ;; Found an element with a matching #+name affiliated keyword.
- (goto-char pos))
- ((and (string-match "^(\\(.*\\))$" s0)
- (save-excursion
+ (catch :coderef-match
+ (while (re-search-forward re nil t)
+ (let ((element (org-element-at-point)))
+ (when (and (memq (org-element-type element)
+ '(example-block src-block))
+ ;; Build proper regexp according to current
+ ;; block's label format.
+ (let ((label-fmt
+ (regexp-quote
+ (or (org-element-property :label-fmt element)
+ org-coderef-label-format))))
+ (save-excursion
+ (beginning-of-line)
+ (looking-at (format ".*?\\(%s\\)[ \t]*$"
+ (format label-fmt coderef))))))
+ (setq type 'dedicated)
+ (goto-char (match-beginning 1))
+ (throw :coderef-match nil))))
+ (goto-char origin)
+ (error "No match for coderef: %s" coderef))))
+ ((string-match "\\`/\\(.*\\)/\\'" normalized)
+ ;; Look for a regular expression.
+ (funcall (if (derived-mode-p 'org-mode) #'org-occur #'org-do-occur)
+ (match-string 1 s)))
+ ;; From here, we handle fuzzy links.
+ ;;
+ ;; Look for targets, only if not in a headline search.
+ ((and (not starred)
+ (let ((target (format "<<%s>>" s-multi-re)))
+ (catch :target-match
+ (goto-char (point-min))
+ (while (re-search-forward target nil t)
+ (backward-char)
+ (let ((context (org-element-context)))
+ (when (eq (org-element-type context) 'target)
+ (setq type 'dedicated)
+ (goto-char (org-element-property :begin context))
+ (throw :target-match t))))
+ nil))))
+ ;; Look for elements named after S, only if not in a headline
+ ;; search.
+ ((and (not starred)
+ (let ((name (format "^[ \t]*#\\+NAME: +%s[ \t]*$" s-single-re)))
+ (catch :name-match
+ (goto-char (point-min))
+ (while (re-search-forward name nil t)
+ (let ((element (org-element-at-point)))
+ (when (equal words
+ (split-string
+ (org-element-property :name element)))
+ (setq type 'dedicated)
+ (beginning-of-line)
+ (throw :name-match t))))
+ nil))))
+ ;; Regular text search. Prefer headlines in Org mode buffers.
+ ;; Ignore COMMENT keyword, TODO keywords, priority cookies,
+ ;; statistics cookies and tags.
+ ((and (derived-mode-p 'org-mode)
+ (let ((title-re
+ (format "%s.*\\(?:%s[ \t]\\)?.*%s"
+ org-outline-regexp-bol
+ org-comment-string
+ (mapconcat #'regexp-quote words ".+")))
+ (cookie-re "\\[[0-9]*\\(?:%\\|/[0-9]*\\)\\]")
+ (comment-re (format "\\`%s[ \t]+" org-comment-string)))
(goto-char (point-min))
- (and
- (re-search-forward
- (concat "[^[]" (regexp-quote
- (format org-coderef-label-format
- (match-string 1 s0))))
- nil t)
- (setq type 'dedicated
- pos (1+ (match-beginning 0))))))
- ;; There is a coderef target for this
- (goto-char pos))
- ((string-match "^/\\(.*\\)/$" s)
- ;; A regular expression
- (cond
- ((derived-mode-p 'org-mode)
- (org-occur (match-string 1 s)))
- (t (org-do-occur (match-string 1 s)))))
- ((and (derived-mode-p 'org-mode) org-link-search-must-match-exact-headline)
- (and (equal (string-to-char s) ?*) (setq s (substring s 1)))
- (goto-char (point-min))
- (cond
- ((let (case-fold-search)
- (re-search-forward (format org-complex-heading-regexp-format
- (regexp-quote s))
- nil t))
- ;; OK, found a match
- (setq type 'dedicated)
- (goto-char (match-beginning 0)))
- ((and (not org-link-search-inhibit-query)
- (eq org-link-search-must-match-exact-headline 'query-to-create)
- (y-or-n-p "No match - create this as a new heading? "))
- (goto-char (point-max))
- (or (bolp) (newline))
- (insert "* " s "\n")
- (beginning-of-line 0))
- (t
- (goto-char pos)
- (error "No match"))))
- (t
- ;; A normal search string
- (when (equal (string-to-char s) ?*)
- ;; Anchor on headlines, post may include tags.
- (setq pre "^\\*+[ \t]+\\(?:\\sw+\\)?[ \t]*"
- post (org-re "[ \t]*\\(?:[ \t]+:[[:alnum:]_@#%:+]:[ \t]*\\)?$")
- s (substring s 1)))
- (remove-text-properties
- 0 (length s)
- '(face nil mouse-face nil keymap nil fontified nil) s)
- ;; Make a series of regular expressions to find a match
- (setq words (org-split-string s "[ \n\r\t]+")
-
- re0 (concat "\\(<<" (regexp-quote s0) ">>\\)")
- re2 (concat markers "\\(" (mapconcat 'downcase words "[ \t]+")
- "\\)" markers)
- re2a_ (concat "\\(" (mapconcat 'downcase words
- "[ \t\r\n]+") "\\)[ \t\r\n]")
- re2a (concat "[ \t\r\n]" re2a_)
- re4_ (concat "\\(" (mapconcat 'downcase words
- "[^a-zA-Z_\r\n]+") "\\)[^a-zA-Z_]")
- re4 (concat "[^a-zA-Z_]" re4_)
-
- re1 (concat pre re2 post)
- re3 (concat pre (if pre re4_ re4) post)
- re5 (concat pre ".*" re4)
- re2 (concat pre re2)
- re2a (concat pre (if pre re2a_ re2a))
- re4 (concat pre (if pre re4_ re4))
- reall (concat "\\(" re0 "\\)\\|\\(" re1 "\\)\\|\\(" re2
- "\\)\\|\\(" re3 "\\)\\|\\(" re4 "\\)\\|\\("
- re5 "\\)"))
- (cond
- ((eq type 'org-occur) (org-occur reall))
- ((eq type 'occur) (org-do-occur (downcase reall) 'cleanup))
- (t (goto-char (point-min))
- (setq type 'fuzzy)
- (if (or (and (org-search-not-self 1 re0 nil t)
- (setq type 'dedicated))
- (org-search-not-self 1 re1 nil t)
- (org-search-not-self 1 re2 nil t)
- (org-search-not-self 1 re2a nil t)
- (org-search-not-self 1 re3 nil t)
- (org-search-not-self 1 re4 nil t)
- (org-search-not-self 1 re5 nil t))
- (goto-char (match-beginning 1))
- (goto-char pos)
- (error "No match"))))))
- (and (derived-mode-p 'org-mode)
- (not stealth)
- (org-show-context 'link-search))
+ (catch :found
+ (while (re-search-forward title-re nil t)
+ (when (equal words
+ (split-string
+ (replace-regexp-in-string
+ cookie-re ""
+ (replace-regexp-in-string
+ comment-re "" (org-get-heading t t)))))
+ (throw :found t)))
+ nil)))
+ (beginning-of-line)
+ (setq type 'dedicated))
+ ;; Offer to create non-existent headline depending on
+ ;; `org-link-search-must-match-exact-headline'.
+ ((and (derived-mode-p 'org-mode)
+ (not org-link-search-inhibit-query)
+ (eq org-link-search-must-match-exact-headline 'query-to-create)
+ (yes-or-no-p "No match - create this as a new heading? "))
+ (goto-char (point-max))
+ (unless (bolp) (newline))
+ (org-insert-heading nil t t)
+ (insert s "\n")
+ (beginning-of-line 0))
+ ;; Only headlines are looked after. No need to process
+ ;; further: throw an error.
+ ((and (derived-mode-p 'org-mode)
+ (or starred org-link-search-must-match-exact-headline))
+ (goto-char origin)
+ (error "No match for fuzzy expression: %s" normalized))
+ ;; Regular text search.
+ ((catch :fuzzy-match
+ (goto-char (point-min))
+ (while (re-search-forward s-multi-re nil t)
+ ;; Skip match if it contains AVOID-POS or it is included in
+ ;; a link with a description but outside the description.
+ (unless (or (and avoid-pos
+ (<= (match-beginning 0) avoid-pos)
+ (> (match-end 0) avoid-pos))
+ (and (save-match-data
+ (org-in-regexp org-bracket-link-regexp))
+ (match-beginning 3)
+ (or (> (match-beginning 3) (point))
+ (<= (match-end 3) (point)))
+ (org-element-lineage
+ (save-match-data (org-element-context))
+ '(link) t)))
+ (goto-char (match-beginning 0))
+ (setq type 'fuzzy)
+ (throw :fuzzy-match t)))
+ nil))
+ ;; All failed. Throw an error.
+ (t (goto-char origin)
+ (error "No match for fuzzy expression: %s" normalized)))
+ ;; Disclose surroundings of match, if appropriate.
+ (when (and (derived-mode-p 'org-mode) (not stealth))
+ (org-show-context 'link-search))
type))
-(defun org-search-not-self (group &rest args)
- "Execute `re-search-forward', but only accept matches that do not
-enclose the position of `org-open-link-marker'."
- (let ((m org-open-link-marker))
- (catch 'exit
- (while (apply #'re-search-forward args)
- (unless (get-text-property (match-end group) 'intangible) ; Emacs 21
- (goto-char (match-end group))
- (if (and (or (not (eq (marker-buffer m) (current-buffer)))
- (> (match-beginning 0) (marker-position m))
- (< (match-end 0) (marker-position m)))
- (save-match-data
- (or (not (org-in-regexp
- org-bracket-link-analytic-regexp 1))
- (not (match-end 4)) ; no description
- (and (<= (match-beginning 4) (point))
- (>= (match-end 4) (point))))))
- (throw 'exit (point))))))))
-
(defun org-get-buffer-for-internal-link (buffer)
"Return a buffer to be used for displaying the link target of internal links."
(cond
((not org-display-internal-link-with-indirect-buffer)
buffer)
- ((string-match "(Clone)$" (buffer-name buffer))
+ ((string-suffix-p "(Clone)" (buffer-name buffer))
(message "Buffer is already a clone, not making another one")
;; we also do not modify visibility in this case
buffer)
@@ -10953,8 +11266,8 @@ to read."
(goto-char (point-min))
(when (re-search-forward "match[a-z]+" nil t)
(setq beg (match-end 0))
- (if (re-search-forward "^[ \t]*[0-9]+" nil t)
- (setq end (1- (match-beginning 0)))))
+ (when (re-search-forward "^[ \t]*[0-9]+" nil t)
+ (setq end (1- (match-beginning 0)))))
(and beg end (let ((inhibit-read-only t)) (delete-region beg end)))
(goto-char (point-min))
(select-window cwin))))
@@ -10962,13 +11275,13 @@ to read."
;;; The mark ring for links jumps
(defvar org-mark-ring nil
- "Mark ring for positions before jumps in Org-mode.")
+ "Mark ring for positions before jumps in Org mode.")
(defvar org-mark-ring-last-goto nil
"Last position in the mark ring used to go back.")
;; Fill and close the ring
(setq org-mark-ring nil org-mark-ring-last-goto nil) ;; in case file is reloaded
-(loop for i from 1 to org-mark-ring-length do
- (push (make-marker) org-mark-ring))
+(dotimes (_ org-mark-ring-length)
+ (push (make-marker) org-mark-ring))
(setcdr (nthcdr (1- org-mark-ring-length) org-mark-ring)
org-mark-ring)
@@ -10982,15 +11295,15 @@ to read."
(or buffer (current-buffer)))
(message "%s"
(substitute-command-keys
- "Position saved to mark ring, go back with \\[org-mark-ring-goto].")))
+ "Position saved to mark ring, go back with \
+`\\[org-mark-ring-goto]'.")))
(defun org-mark-ring-goto (&optional n)
"Jump to the previous position in the mark ring.
With prefix arg N, jump back that many stored positions. When
called several times in succession, walk through the entire ring.
-Org-mode commands jumping to a different position in the current file,
-or to another Org-mode file, automatically push the old position
-onto the ring."
+Org mode commands jumping to a different position in the current file,
+or to another Org file, automatically push the old position onto the ring."
(interactive "p")
(let (p m)
(if (eq last-command this-command)
@@ -10998,25 +11311,19 @@ onto the ring."
(setq p org-mark-ring))
(setq org-mark-ring-last-goto p)
(setq m (car p))
- (org-pop-to-buffer-same-window (marker-buffer m))
+ (pop-to-buffer-same-window (marker-buffer m))
(goto-char m)
- (if (or (outline-invisible-p) (org-invisible-p2)) (org-show-context 'mark-goto))))
+ (when (or (org-invisible-p) (org-invisible-p2)) (org-show-context 'mark-goto))))
-(defun org-remove-angle-brackets (s)
- (if (equal (substring s 0 1) "<") (setq s (substring s 1)))
- (if (equal (substring s -1) ">") (setq s (substring s 0 -1)))
- s)
(defun org-add-angle-brackets (s)
- (if (equal (substring s 0 1) "<") nil (setq s (concat "<" s)))
- (if (equal (substring s -1) ">") nil (setq s (concat s ">")))
- s)
-(defun org-remove-double-quotes (s)
- (if (equal (substring s 0 1) "\"") (setq s (substring s 1)))
- (if (equal (substring s -1) "\"") (setq s (substring s 0 -1)))
+ (unless (equal (substring s 0 1) "<") (setq s (concat "<" s)))
+ (unless (equal (substring s -1) ">") (setq s (concat s ">")))
s)
;;; Following specific links
+(defvar org-agenda-buffer-tmp-name)
+(defvar org-agenda-start-on-weekday)
(defun org-follow-timestamp-link ()
"Open an agenda view for the time-stamp date/range at point."
(cond
@@ -11071,43 +11378,40 @@ If the file does not exist, an error is thrown."
buffer-file-name
(substitute-in-file-name (expand-file-name path))))
(file-apps (append org-file-apps (org-default-apps)))
- (apps (org-remove-if
+ (apps (cl-remove-if
'org-file-apps-entry-match-against-dlink-p file-apps))
- (apps-dlink (org-remove-if-not
+ (apps-dlink (cl-remove-if-not
'org-file-apps-entry-match-against-dlink-p file-apps))
(remp (and (assq 'remote apps) (org-file-remote-p file)))
- (dirp (if remp nil (file-directory-p file)))
+ (dirp (unless remp (file-directory-p file)))
(file (if (and dirp org-open-directory-means-index-dot-org)
(concat (file-name-as-directory file) "index.org")
file))
(a-m-a-p (assq 'auto-mode apps))
(dfile (downcase file))
- ;; reconstruct the original file: link from the PATH, LINE and SEARCH args
- (link (cond ((and (eq line nil)
- (eq search nil))
- file)
- (line
- (concat file "::" (number-to-string line)))
- (search
- (concat file "::" search))))
+ ;; Reconstruct the original link from the PATH, LINE and
+ ;; SEARCH args.
+ (link (cond (line (concat file "::" (number-to-string line)))
+ (search (concat file "::" search))
+ (t file)))
(dlink (downcase link))
(old-buffer (current-buffer))
(old-pos (point))
(old-mode major-mode)
- ext cmd link-match-data)
- (if (string-match "^.*\\.\\([a-zA-Z0-9]+\\.gz\\)$" dfile)
- (setq ext (match-string 1 dfile))
- (if (string-match "^.*\\.\\([a-zA-Z0-9]+\\)$" dfile)
- (setq ext (match-string 1 dfile))))
+ (ext
+ (and (string-match "\\`.*?\\.\\([a-zA-Z0-9]+\\(\\.gz\\)?\\)\\'" dfile)
+ (match-string 1 dfile)))
+ cmd link-match-data)
(cond
((member in-emacs '((16) system))
- (setq cmd (cdr (assoc 'system apps))))
+ (setq cmd (cdr (assq 'system apps))))
(in-emacs (setq cmd 'emacs))
(t
- (setq cmd (or (and remp (cdr (assoc 'remote apps)))
- (and dirp (cdr (assoc 'directory apps)))
- ; first, try matching against apps-dlink
- ; if we get a match here, store the match data for later
+ (setq cmd (or (and remp (cdr (assq 'remote apps)))
+ (and dirp (cdr (assq 'directory apps)))
+ ;; First, try matching against apps-dlink if we
+ ;; get a match here, store the match data for
+ ;; later.
(let ((match (assoc-default dlink apps-dlink
'string-match)))
(if match
@@ -11120,9 +11424,9 @@ If the file does not exist, an error is thrown."
(assoc-default dfile (org-apps-regexp-alist apps a-m-a-p)
'string-match)
(cdr (assoc ext apps))
- (cdr (assoc t apps))))))
+ (cdr (assq t apps))))))
(when (eq cmd 'system)
- (setq cmd (cdr (assoc 'system apps))))
+ (setq cmd (cdr (assq 'system apps))))
(when (eq cmd 'default)
(setq cmd (cdr (assoc t apps))))
(when (eq cmd 'mailcap)
@@ -11133,21 +11437,20 @@ If the file does not exist, an error is thrown."
(if (stringp command)
(setq cmd command)
(setq cmd 'emacs))))
- (if (and (not (eq cmd 'emacs)) ; Emacs has no problems with non-ex files
- (not (file-exists-p file))
- (not org-open-non-existing-files))
- (user-error "No such file: %s" file))
+ (when (and (not (eq cmd 'emacs)) ; Emacs has no problems with non-ex files
+ (not (file-exists-p file))
+ (not org-open-non-existing-files))
+ (user-error "No such file: %s" file))
(cond
((and (stringp cmd) (not (string-match "^\\s-*$" cmd)))
;; Remove quotes around the file name - we'll use shell-quote-argument.
(while (string-match "['\"]%s['\"]" cmd)
(setq cmd (replace-match "%s" t t cmd)))
- (while (string-match "%s" cmd)
- (setq cmd (replace-match
- (save-match-data
- (shell-quote-argument
- (convert-standard-filename file)))
- t t cmd)))
+ (setq cmd (replace-regexp-in-string
+ "%s"
+ (shell-quote-argument (convert-standard-filename file))
+ cmd
+ nil t))
;; Replace "%1", "%2" etc. in command with group matches from regex
(save-match-data
@@ -11169,17 +11472,33 @@ If the file does not exist, an error is thrown."
(eq cmd 'emacs))
(funcall (cdr (assq 'file org-link-frame-setup)) file)
(widen)
- (if line (org-goto-line line)
- (if search (org-link-search search))))
+ (cond (line (org-goto-line line)
+ (when (derived-mode-p 'org-mode) (org-reveal)))
+ (search (org-link-search search))))
+ ((functionp cmd)
+ (save-match-data
+ (set-match-data link-match-data)
+ (condition-case nil
+ (funcall cmd file link)
+ ;; FIXME: Remove this check when most default installations
+ ;; of Emacs have at least Org 9.0.
+ ((debug wrong-number-of-arguments wrong-type-argument
+ invalid-function)
+ (user-error "Please see Org News for version 9.0 about \
+`org-file-apps'--Lisp error: %S" cmd)))))
((consp cmd)
- (let ((file (convert-standard-filename file)))
- (save-match-data
- (set-match-data link-match-data)
- (eval cmd))))
+ ;; FIXME: Remove this check when most default installations of
+ ;; Emacs have at least Org 9.0.
+ ;; Heads-up instead of silently fall back to
+ ;; `org-link-frame-setup' for an old usage of `org-file-apps'
+ ;; with sexp instead of a function for `cmd'.
+ (user-error "Please see Org News for version 9.0 about \
+`org-file-apps'--Error: Deprecated usage of %S" cmd))
(t (funcall (cdr (assq 'file org-link-frame-setup)) file)))
- (and (derived-mode-p 'org-mode) (eq old-mode 'org-mode)
- (or (not (equal old-buffer (current-buffer)))
- (not (equal old-pos (point))))
+ (and (derived-mode-p 'org-mode)
+ (eq old-mode 'org-mode)
+ (or (not (eq old-buffer (current-buffer)))
+ (not (eq old-pos (point))))
(org-mark-ring-push old-pos old-buffer))))
(defun org-file-apps-entry-match-against-dlink-p (entry)
@@ -11220,16 +11539,15 @@ be opened in Emacs."
(append
(delq nil
(mapcar (lambda (x)
- (if (not (stringp (car x)))
- nil
+ (unless (not (stringp (car x)))
(if (string-match "\\W" (car x))
x
(cons (concat "\\." (car x) "\\'") (cdr x)))))
list))
- (if add-auto-mode
- (mapcar (lambda (x) (cons (car x) 'emacs)) auto-mode-alist))))
+ (when add-auto-mode
+ (mapcar (lambda (x) (cons (car x) 'emacs)) auto-mode-alist))))
-(defvar ange-ftp-name-format) ; to silence the XEmacs compiler.
+(defvar ange-ftp-name-format)
(defun org-file-remote-p (file)
"Test whether FILE specifies a location on a remote system.
Return non-nil if the location is indeed remote.
@@ -11262,8 +11580,8 @@ on the system \"/user@host:\"."
((not (listp org-reverse-note-order)) nil)
(t (catch 'exit
(dolist (entry org-reverse-note-order)
- (if (string-match (car entry) buffer-file-name)
- (throw 'exit (cdr entry))))))))
+ (when (string-match (car entry) buffer-file-name)
+ (throw 'exit (cdr entry))))))))
(defvar org-refile-target-table nil
"The list of refile targets, created by `org-refile'.")
@@ -11288,7 +11606,7 @@ on the system \"/user@host:\"."
(defun org-refile-cache-clear ()
"Clear the refile cache and disable all the markers."
- (mapc (lambda (m) (move-marker m nil)) org-refile-markers)
+ (dolist (m org-refile-markers) (move-marker m nil))
(setq org-refile-markers nil)
(setq org-refile-cache nil)
(message "Refile cache has been cleared"))
@@ -11323,17 +11641,23 @@ on the system \"/user@host:\"."
org-refile-cache))))
(and set (org-refile-cache-check-set set) set)))))
-(defun org-refile-get-targets (&optional default-buffer excluded-entries)
+(defvar org-outline-path-cache nil
+ "Alist between buffer positions and outline paths.
+It value is an alist (POSITION . PATH) where POSITION is the
+buffer position at the beginning of an entry and PATH is a list
+of strings describing the outline path for that entry, in reverse
+order.")
+
+(defun org-refile-get-targets (&optional default-buffer)
"Produce a table with refile targets."
(let ((case-fold-search nil)
;; otherwise org confuses "TODO" as a kw and "Todo" as a word
(entries (or org-refile-targets '((nil . (:level . 1)))))
- targets tgs txt re files desc descre fast-path-p level pos0)
+ targets tgs files desc descre)
(message "Getting targets...")
(with-current-buffer (or default-buffer (current-buffer))
(dolist (entry entries)
(setq files (car entry) desc (cdr entry))
- (setq fast-path-p nil)
(cond
((null files) (setq files (list (current-buffer))))
((eq files 'org-agenda-files)
@@ -11342,7 +11666,7 @@ on the system \"/user@host:\"."
(setq files (funcall files)))
((and (symbolp files) (boundp files))
(setq files (symbol-value files))))
- (if (stringp files) (setq files (list files)))
+ (when (stringp files) (setq files (list files)))
(cond
((eq (car desc) :tag)
(setq descre (concat "^\\*+[ \t]+.*?:" (regexp-quote (cdr desc)) ":")))
@@ -11357,7 +11681,6 @@ on the system \"/user@host:\"."
(cdr desc)))
"\\}[ \t]")))
((eq (car desc) :maxlevel)
- (setq fast-path-p t)
(setq descre (concat "^\\*\\{1," (number-to-string
(if org-odd-levels-only
(1- (* 2 (cdr desc)))
@@ -11365,99 +11688,113 @@ on the system \"/user@host:\"."
"\\}[ \t]")))
(t (error "Bad refiling target description %s" desc)))
(dolist (f files)
- (with-current-buffer
- (if (bufferp f) f (org-get-agenda-file-buffer f))
+ (with-current-buffer (if (bufferp f) f (org-get-agenda-file-buffer f))
(or
(setq tgs (org-refile-cache-get (buffer-file-name) descre))
(progn
- (if (bufferp f) (setq f (buffer-file-name
- (buffer-base-buffer f))))
+ (when (bufferp f)
+ (setq f (buffer-file-name (buffer-base-buffer f))))
(setq f (and f (expand-file-name f)))
- (if (eq org-refile-use-outline-path 'file)
- (push (list (file-name-nondirectory f) f nil nil) tgs))
- (save-excursion
- (save-restriction
- (widen)
- (goto-char (point-min))
- (while (re-search-forward descre nil t)
- (goto-char (setq pos0 (point-at-bol)))
- (catch 'next
- (when org-refile-target-verify-function
- (save-match-data
- (or (funcall org-refile-target-verify-function)
- (throw 'next t))))
- (when (and (looking-at org-complex-heading-regexp)
- (not (member (match-string 4) excluded-entries))
- (match-string 4))
- (setq level (org-reduced-level
- (- (match-end 1) (match-beginning 1)))
- txt (org-link-display-format (match-string 4))
- txt (replace-regexp-in-string "\\( *[[0-9]+/?[0-9]*%?]\\)+$" "" txt)
- re (format org-complex-heading-regexp-format
- (regexp-quote (match-string 4))))
- (when org-refile-use-outline-path
- (setq txt (mapconcat
- 'org-protect-slash
- (append
- (if (eq org-refile-use-outline-path
- 'file)
- (list (file-name-nondirectory
- (buffer-file-name
- (buffer-base-buffer))))
- (if (eq org-refile-use-outline-path
- 'full-file-path)
- (list (buffer-file-name
- (buffer-base-buffer)))))
- (org-get-outline-path fast-path-p
- level txt)
- (list txt))
- "/")))
- (push (list txt f re (org-refile-marker (point)))
- tgs)))
- (when (= (point) pos0)
- ;; verification function has not moved point
- (goto-char (point-at-eol))))))))
+ (when (eq org-refile-use-outline-path 'file)
+ (push (list (file-name-nondirectory f) f nil nil) tgs))
+ (org-with-wide-buffer
+ (goto-char (point-min))
+ (setq org-outline-path-cache nil)
+ (while (re-search-forward descre nil t)
+ (beginning-of-line)
+ (let ((case-fold-search nil))
+ (looking-at org-complex-heading-regexp))
+ (let ((begin (point))
+ (heading (match-string-no-properties 4)))
+ (unless (or (and
+ org-refile-target-verify-function
+ (not
+ (funcall org-refile-target-verify-function)))
+ (not heading))
+ (let ((re (format org-complex-heading-regexp-format
+ (regexp-quote heading)))
+ (target
+ (if (not org-refile-use-outline-path) heading
+ (mapconcat
+ #'org-protect-slash
+ (append
+ (pcase org-refile-use-outline-path
+ (`file (list (file-name-nondirectory
+ (buffer-file-name
+ (buffer-base-buffer)))))
+ (`full-file-path
+ (list (buffer-file-name
+ (buffer-base-buffer))))
+ (_ nil))
+ (org-get-outline-path t t))
+ "/"))))
+ (push (list target f re (org-refile-marker (point)))
+ tgs)))
+ (when (= (point) begin)
+ ;; Verification function has not moved point.
+ (end-of-line)))))))
(when org-refile-use-cache
(org-refile-cache-put tgs (buffer-file-name) descre))
(setq targets (append tgs targets))))))
(message "Getting targets...done")
- (nreverse targets)))
+ (delete-dups (nreverse targets))))
(defun org-protect-slash (s)
- (while (string-match "/" s)
- (setq s (replace-match "\\" t t s)))
- s)
-
-(defvar org-olpa (make-vector 20 nil))
-
-(defun org-get-outline-path (&optional fastp level heading)
- "Return the outline path to the current entry, as a list.
-
-The parameters FASTP, LEVEL, and HEADING are for use by a scanner
-routine which makes outline path derivations for an entire file,
-avoiding backtracing. Refile target collection makes use of that."
- (if fastp
- (progn
- (if (> level 19)
- (error "Outline path failure, more than 19 levels"))
- (loop for i from level upto 19 do
- (aset org-olpa i nil))
- (prog1
- (delq nil (append org-olpa nil))
- (aset org-olpa level heading)))
- (let (rtn case-fold-search)
- (save-excursion
- (save-restriction
- (widen)
- (while (org-up-heading-safe)
- (when (looking-at org-complex-heading-regexp)
- (push (org-trim
- (replace-regexp-in-string
- ;; Remove statistical/checkboxes cookies
- "\\[[0-9]+%\\]\\|\\[[0-9]+/[0-9]+\\]" ""
- (org-match-string-no-properties 4)))
- rtn)))
- rtn)))))
+ (replace-regexp-in-string "/" "\\/" s nil t))
+
+(defun org--get-outline-path-1 (&optional use-cache)
+ "Return outline path to current headline.
+
+Outline path is a list of strings, in reverse order. When
+optional argument USE-CACHE is non-nil, make use of a cache. See
+`org-get-outline-path' for details.
+
+Assume buffer is widened and point is on a headline."
+ (or (and use-cache (cdr (assq (point) org-outline-path-cache)))
+ (let ((p (point))
+ (heading (let ((case-fold-search nil))
+ (looking-at org-complex-heading-regexp)
+ (if (not (match-end 4)) ""
+ ;; Remove statistics cookies.
+ (org-trim
+ (org-link-display-format
+ (replace-regexp-in-string
+ "\\[[0-9]+%\\]\\|\\[[0-9]+/[0-9]+\\]" ""
+ (match-string-no-properties 4))))))))
+ (if (org-up-heading-safe)
+ (let ((path (cons heading (org--get-outline-path-1 use-cache))))
+ (when use-cache
+ (push (cons p path) org-outline-path-cache))
+ path)
+ ;; This is a new root node. Since we assume we are moving
+ ;; forward, we can drop previous cache so as to limit number
+ ;; of associations there.
+ (let ((path (list heading)))
+ (when use-cache (setq org-outline-path-cache (list (cons p path))))
+ path)))))
+
+(defun org-get-outline-path (&optional with-self use-cache)
+ "Return the outline path to the current entry.
+
+An outline path is a list of ancestors for current headline, as
+a list of strings. Statistics cookies are removed and links are
+replaced with their description, if any, or their path otherwise.
+
+When optional argument WITH-SELF is non-nil, the path also
+includes the current headline.
+
+When optional argument USE-CACHE is non-nil, cache outline paths
+between calls to this function so as to avoid backtracking. This
+argument is useful when planning to find more than one outline
+path in the same document. In that case, there are two
+conditions to satisfy:
+ - `org-outline-path-cache' is set to nil before starting the
+ process;
+ - outline paths are computed by increasing buffer positions."
+ (org-with-wide-buffer
+ (and (or (and with-self (org-back-to-heading t))
+ (org-up-heading-safe))
+ (reverse (org--get-outline-path-1 use-cache)))))
(defun org-format-outline-path (path &optional width prefix separator)
"Format the outline path PATH for display.
@@ -11467,38 +11804,28 @@ such as the file name.
SEPARATOR is inserted between the different parts of the path,
the default is \"/\"."
(setq width (or width 79))
- (if prefix (setq width (- width (length prefix))))
- (if (not path)
- (or prefix "")
- (let* ((nsteps (length path))
- (total-width (+ nsteps (apply '+ (mapcar 'length path))))
- (maxwidth (if (<= total-width width)
- 10000 ;; everything fits
- ;; we need to shorten the level headings
- (/ (- width nsteps) nsteps)))
- (org-odd-levels-only nil)
- (n 0)
- (total (1+ (length prefix))))
- (setq maxwidth (max maxwidth 10))
- (concat prefix
- (if prefix (or separator "/"))
- (mapconcat
- (lambda (h)
- (setq n (1+ n))
- (if (and (= n nsteps) (< maxwidth 10000))
- (setq maxwidth (- total-width total)))
- (if (< (length h) maxwidth)
- (progn (setq total (+ total (length h) 1)) h)
- (setq h (substring h 0 (- maxwidth 2))
- total (+ total maxwidth 1))
- (if (string-match "[ \t]+\\'" h)
- (setq h (substring h 0 (match-beginning 0))))
- (setq h (concat h "..")))
- (org-add-props h nil 'face
- (nth (% (1- n) org-n-level-faces)
- org-level-faces))
- h)
- path (or separator "/"))))))
+ (setq path (delq nil path))
+ (unless (> width 0)
+ (user-error "Argument `width' must be positive"))
+ (setq separator (or separator "/"))
+ (let* ((org-odd-levels-only nil)
+ (fpath (concat
+ prefix (and prefix path separator)
+ (mapconcat
+ (lambda (s) (replace-regexp-in-string "[ \t]+\\'" "" s))
+ (cl-loop for head in path
+ for n from 0
+ collect (org-add-props
+ head nil 'face
+ (nth (% n org-n-level-faces) org-level-faces)))
+ separator))))
+ (when (> (length fpath) width)
+ (if (< width 7)
+ ;; It's unlikely that `width' will be this small, but don't
+ ;; waste characters by adding ".." if it is.
+ (setq fpath (substring fpath 0 width))
+ (setf (substring fpath (- width 2)) "..")))
+ fpath))
(defun org-display-outline-path (&optional file current separator just-return-string)
"Display the current outline path in the echo area.
@@ -11513,10 +11840,10 @@ If JUST-RETURN-STRING is non-nil, return a string, don't display a message."
(bfn (buffer-file-name (buffer-base-buffer)))
(path (and (derived-mode-p 'org-mode) (org-get-outline-path)))
res)
- (if current (setq path (append path
- (save-excursion
- (org-back-to-heading t)
- (if (looking-at org-complex-heading-regexp)
+ (when current (setq path (append path
+ (save-excursion
+ (org-back-to-heading t)
+ (when (looking-at org-complex-heading-regexp)
(list (match-string 4)))))))
(setq res
(org-format-outline-path
@@ -11546,25 +11873,27 @@ the *old* location.")
(let ((org-refile-keep t))
(funcall 'org-refile nil nil nil "Copy")))
-(defun org-refile (&optional goto default-buffer rfloc msg)
+(defun org-refile (&optional arg default-buffer rfloc msg)
"Move the entry or entries at point to another heading.
+
The list of target headings is compiled using the information in
`org-refile-targets', which see.
-At the target location, the entry is filed as a subitem of the target
-heading. Depending on `org-reverse-note-order', the new subitem will
-either be the first or the last subitem.
+At the target location, the entry is filed as a subitem of the
+target heading. Depending on `org-reverse-note-order', the new
+subitem will either be the first or the last subitem.
-If there is an active region, all entries in that region will be moved.
-However, the region must fulfill the requirement that the first heading
-is the first one sets the top-level of the moved text - at most siblings
-below it are allowed.
+If there is an active region, all entries in that region will be
+refiled. However, the region must fulfill the requirement that
+the first heading sets the top-level of the moved text.
-With prefix arg GOTO, the command will only visit the target location
+With a `\\[universal-argument]' ARG, the command will only visit the target \
+location
and not actually move anything.
-With a double prefix arg \\[universal-argument] \\[universal-argument], \
-go to the location where the last refiling operation has put the subtree.
+With a prefix `\\[universal-argument] \\[universal-argument]', go to the \
+location where the last
+refiling operation has put the subtree.
With a numeric prefix argument of `2', refile to the running clock.
@@ -11578,26 +11907,23 @@ RFLOC can be a refile location obtained in a different way.
MSG is a string to replace \"Refile\" in the default prompt with
another verb. E.g. `org-copy' sets this parameter to \"Copy\".
-See also `org-refile-use-outline-path' and `org-completion-use-ido'.
+See also `org-refile-use-outline-path'.
-If you are using target caching (see `org-refile-use-cache'),
-you have to clear the target cache in order to find new targets.
-This can be done with a 0 prefix (`C-0 C-c C-w') or a triple
+If you are using target caching (see `org-refile-use-cache'), you
+have to clear the target cache in order to find new targets.
+This can be done with a `0' prefix (`C-0 C-c C-w') or a triple
prefix argument (`C-u C-u C-u C-c C-w')."
-
(interactive "P")
- (if (member goto '(0 (64)))
+ (if (member arg '(0 (64)))
(org-refile-cache-clear)
(let* ((actionmsg (cond (msg msg)
- ((equal goto 3) "Refile (and keep)")
+ ((equal arg 3) "Refile (and keep)")
(t "Refile")))
- (cbuf (current-buffer))
(regionp (org-region-active-p))
(region-start (and regionp (region-beginning)))
(region-end (and regionp (region-end)))
- (filename (buffer-file-name (buffer-base-buffer cbuf)))
- (org-refile-keep (if (equal goto 3) t org-refile-keep))
- pos it nbuf file re level reversed)
+ (org-refile-keep (if (equal arg 3) t org-refile-keep))
+ pos it nbuf file level reversed)
(setq last-command nil)
(when regionp
(goto-char region-start)
@@ -11610,10 +11936,10 @@ prefix argument (`C-u C-u C-u C-c C-w')."
(org-toggle-heading)
(setq region-end (+ (- (point-at-eol) s) region-end)))))
(user-error "The region is not a (sequence of) subtree(s)")))
- (if (equal goto '(16))
+ (if (equal arg '(16))
(org-refile-goto-last-stored)
(when (or
- (and (equal goto 2)
+ (and (equal arg 2)
org-clock-hd-marker (marker-buffer org-clock-hd-marker)
(prog1
(setq it (list (or org-clock-heading "running clock")
@@ -11621,43 +11947,44 @@ prefix argument (`C-u C-u C-u C-c C-w')."
(marker-buffer org-clock-hd-marker))
""
(marker-position org-clock-hd-marker)))
- (setq goto nil)))
- (setq it (or rfloc
- (let (heading-text)
- (save-excursion
- (unless (and goto (listp goto))
- (org-back-to-heading t)
- (setq heading-text
- (nth 4 (org-heading-components))))
-
- (org-refile-get-location
- (cond ((and goto (listp goto)) "Goto")
- (regionp (concat actionmsg " region to"))
- (t (concat actionmsg " subtree \""
- heading-text "\" to")))
- default-buffer
- (and (not (equal '(4) goto))
- org-refile-allow-creating-parent-nodes)
- goto))))))
+ (setq arg nil)))
+ (setq it
+ (or rfloc
+ (let (heading-text)
+ (save-excursion
+ (unless (and arg (listp arg))
+ (org-back-to-heading t)
+ (setq heading-text
+ (replace-regexp-in-string
+ org-bracket-link-regexp
+ "\\3"
+ (or (nth 4 (org-heading-components))
+ ""))))
+ (org-refile-get-location
+ (cond ((and arg (listp arg)) "Goto")
+ (regionp (concat actionmsg " region to"))
+ (t (concat actionmsg " subtree \""
+ heading-text "\" to")))
+ default-buffer
+ (and (not (equal '(4) arg))
+ org-refile-allow-creating-parent-nodes)))))))
(setq file (nth 1 it)
- re (nth 2 it)
pos (nth 3 it))
- (if (and (not goto)
- pos
- (equal (buffer-file-name) file)
- (if regionp
- (and (>= pos region-start)
- (<= pos region-end))
- (and (>= pos (point))
- (< pos (save-excursion
- (org-end-of-subtree t t))))))
- (error "Cannot refile to position inside the tree or region"))
-
+ (when (and (not arg)
+ pos
+ (equal (buffer-file-name) file)
+ (if regionp
+ (and (>= pos region-start)
+ (<= pos region-end))
+ (and (>= pos (point))
+ (< pos (save-excursion
+ (org-end-of-subtree t t))))))
+ (error "Cannot refile to position inside the tree or region"))
(setq nbuf (or (find-buffer-visiting file)
(find-file-noselect file)))
- (if (and goto (not (equal goto 3)))
+ (if (and arg (not (equal arg 3)))
(progn
- (org-pop-to-buffer-same-window nbuf)
+ (pop-to-buffer-same-window nbuf)
(goto-char pos)
(org-show-context 'org-goto))
(if regionp
@@ -11668,50 +11995,48 @@ prefix argument (`C-u C-u C-u C-c C-w')."
(with-current-buffer (setq nbuf (or (find-buffer-visiting file)
(find-file-noselect file)))
(setq reversed (org-notes-order-reversed-p))
- (save-excursion
- (save-restriction
- (widen)
- (if pos
- (progn
- (goto-char pos)
- (looking-at org-outline-regexp)
- (setq level (org-get-valid-level (funcall outline-level) 1))
- (goto-char
- (if reversed
- (or (outline-next-heading) (point-max))
- (or (save-excursion (org-get-next-sibling))
- (org-end-of-subtree t t)
- (point-max)))))
- (setq level 1)
- (if (not reversed)
- (goto-char (point-max))
- (goto-char (point-min))
- (or (outline-next-heading) (goto-char (point-max)))))
- (if (not (bolp)) (newline))
- (org-paste-subtree level)
- (when org-log-refile
- (org-add-log-setup 'refile nil nil 'findpos org-log-refile)
- (unless (eq org-log-refile 'note)
- (save-excursion (org-add-log-note))))
- (and org-auto-align-tags
- (let ((org-loop-over-headlines-in-active-region nil))
- (org-set-tags nil t)))
- (let ((bookmark-name (plist-get org-bookmark-names-plist
- :last-refile)))
- (when bookmark-name
- (with-demoted-errors
- (bookmark-set bookmark-name))))
- ;; If we are refiling for capture, make sure that the
- ;; last-capture pointers point here
- (when (org-bound-and-true-p org-refile-for-capture)
- (let ((bookmark-name (plist-get org-bookmark-names-plist
- :last-capture-marker)))
- (when bookmark-name
- (with-demoted-errors
- (bookmark-set bookmark-name))))
- (move-marker org-capture-last-stored-marker (point)))
- (if (fboundp 'deactivate-mark) (deactivate-mark))
- (run-hooks 'org-after-refile-insert-hook))))
+ (org-with-wide-buffer
+ (if pos
+ (progn
+ (goto-char pos)
+ (looking-at org-outline-regexp)
+ (setq level (org-get-valid-level (funcall outline-level) 1))
+ (goto-char
+ (if reversed
+ (or (outline-next-heading) (point-max))
+ (or (save-excursion (org-get-next-sibling))
+ (org-end-of-subtree t t)
+ (point-max)))))
+ (setq level 1)
+ (if (not reversed)
+ (goto-char (point-max))
+ (goto-char (point-min))
+ (or (outline-next-heading) (goto-char (point-max)))))
+ (unless (bolp) (newline))
+ (org-paste-subtree level nil nil t)
+ (when org-log-refile
+ (org-add-log-setup 'refile nil nil org-log-refile)
+ (unless (eq org-log-refile 'note)
+ (save-excursion (org-add-log-note))))
+ (and org-auto-align-tags
+ (let ((org-loop-over-headlines-in-active-region nil))
+ (org-set-tags nil t)))
+ (let ((bookmark-name (plist-get org-bookmark-names-plist
+ :last-refile)))
+ (when bookmark-name
+ (with-demoted-errors
+ (bookmark-set bookmark-name))))
+ ;; If we are refiling for capture, make sure that the
+ ;; last-capture pointers point here
+ (when (bound-and-true-p org-capture-is-refiling)
+ (let ((bookmark-name (plist-get org-bookmark-names-plist
+ :last-capture-marker)))
+ (when bookmark-name
+ (with-demoted-errors
+ (bookmark-set bookmark-name))))
+ (move-marker org-capture-last-stored-marker (point)))
+ (when (fboundp 'deactivate-mark) (deactivate-mark))
+ (run-hooks 'org-after-refile-insert-hook)))
(unless org-refile-keep
(if regionp
(delete-region (point) (+ (point) (- region-end region-start)))
@@ -11726,7 +12051,7 @@ prefix argument (`C-u C-u C-u C-c C-w')."
(defun org-refile-goto-last-stored ()
"Go to the location where the last refile was stored."
(interactive)
- (bookmark-jump "org-refile-last-stored")
+ (bookmark-jump (plist-get org-bookmark-names-plist :last-refile))
(message "This is the location of the last refile"))
(defun org-refile--get-location (refloc tbl)
@@ -11740,35 +12065,22 @@ Also check `org-refile-target-table'."
(list (replace-regexp-in-string "/$" "" refloc)
(replace-regexp-in-string "\\([^/]\\)$" "\\1/" refloc))))))
-(defun org-refile-get-location (&optional prompt default-buffer new-nodes
- no-exclude)
+(defun org-refile-get-location (&optional prompt default-buffer new-nodes)
"Prompt the user for a refile location, using PROMPT.
PROMPT should not be suffixed with a colon and a space, because
this function appends the default value from
-`org-refile-history' automatically, if that is not empty.
-When NO-EXCLUDE is set, do not exclude headlines in the current subtree,
-this is used for the GOTO interface."
+`org-refile-history' automatically, if that is not empty."
(let ((org-refile-targets org-refile-targets)
- (org-refile-use-outline-path org-refile-use-outline-path)
- excluded-entries)
- (when (and (derived-mode-p 'org-mode)
- (not org-refile-use-cache)
- (not no-exclude))
- (org-map-tree
- (lambda()
- (setq excluded-entries
- (append excluded-entries (list (org-get-heading t t)))))))
- (setq org-refile-target-table
- (org-refile-get-targets default-buffer excluded-entries)))
+ (org-refile-use-outline-path org-refile-use-outline-path))
+ (setq org-refile-target-table (org-refile-get-targets default-buffer)))
(unless org-refile-target-table
(user-error "No refile targets"))
(let* ((cbuf (current-buffer))
- (partial-completion-mode nil)
(cfn (buffer-file-name (buffer-base-buffer cbuf)))
(cfunc (if (and org-refile-use-outline-path
org-outline-path-complete-in-steps)
- 'org-olpath-completing-read
- 'org-icompleting-read))
+ #'org-olpath-completing-read
+ #'completing-read))
(extra (if org-refile-use-outline-path "/" ""))
(cbnex (concat (buffer-name) extra))
(filename (and cfn (expand-file-name cfn)))
@@ -11803,8 +12115,8 @@ this is used for the GOTO interface."
(cons (car pa) (if (assoc (car org-refile-history) tbl)
org-refile-history
(cdr org-refile-history))))
- (if (equal (car org-refile-history) (nth 1 org-refile-history))
- (pop org-refile-history)))
+ (when (equal (car org-refile-history) (nth 1 org-refile-history))
+ (pop org-refile-history)))
pa)
(if (string-match "\\`\\(.*\\)/\\([^/]+\\)\\'" answ)
(progn
@@ -11827,20 +12139,18 @@ this is used for the GOTO interface."
(pos (nth 3 refile-pointer))
buffer)
(if (and (not (markerp pos)) (not file))
- (user-error "Please save the buffer to a file before refiling")
+ (user-error "Please indicate a target file in the refile path")
(when (org-string-nw-p re)
(setq buffer (if (markerp pos)
(marker-buffer pos)
(or (find-buffer-visiting file)
(find-file-noselect file))))
(with-current-buffer buffer
- (save-excursion
- (save-restriction
- (widen)
- (goto-char pos)
- (beginning-of-line 1)
- (unless (org-looking-at-p re)
- (user-error "Invalid refile position, please clear the cache with `C-0 C-c C-w' before refiling")))))))))
+ (org-with-wide-buffer
+ (goto-char pos)
+ (beginning-of-line 1)
+ (unless (looking-at-p re)
+ (user-error "Invalid refile position, please clear the cache with `C-0 C-c C-w' before refiling"))))))))
(defun org-refile-new-child (parent-target child)
"Use refile target PARENT-TARGET to add new CHILD below it."
@@ -11851,53 +12161,43 @@ this is used for the GOTO interface."
level)
(with-current-buffer (or (find-buffer-visiting file)
(find-file-noselect file))
- (save-excursion
- (save-restriction
- (widen)
- (if pos
- (goto-char pos)
- (goto-char (point-max))
- (if (not (bolp)) (newline)))
- (when (looking-at org-outline-regexp)
- (setq level (funcall outline-level))
- (org-end-of-subtree t t))
- (org-back-over-empty-lines)
- (insert "\n" (make-string
- (if pos (org-get-valid-level level 1) 1) ?*)
- " " child "\n")
- (beginning-of-line 0)
- (list (concat (car parent-target) "/" child) file "" (point)))))))
+ (org-with-wide-buffer
+ (if pos
+ (goto-char pos)
+ (goto-char (point-max))
+ (unless (bolp) (newline)))
+ (when (looking-at org-outline-regexp)
+ (setq level (funcall outline-level))
+ (org-end-of-subtree t t))
+ (org-back-over-empty-lines)
+ (insert "\n" (make-string
+ (if pos (org-get-valid-level level 1) 1) ?*)
+ " " child "\n")
+ (beginning-of-line 0)
+ (list (concat (car parent-target) "/" child) file "" (point))))))
(defun org-olpath-completing-read (prompt collection &rest args)
"Read an outline path like a file name."
- (let ((thetable collection)
- (org-completion-use-ido nil) ; does not work with ido.
- (org-completion-use-iswitchb nil)) ; or iswitchb
- (apply
- 'org-icompleting-read prompt
- (lambda (string predicate &optional flag)
- (let (rtn r f (l (length string)))
- (cond
- ((eq flag nil)
- ;; try completion
- (try-completion string thetable))
- ((eq flag t)
- ;; all-completions
- (setq rtn (all-completions string thetable predicate))
- (mapcar
- (lambda (x)
- (setq r (substring x l))
- (if (string-match " ([^)]*)$" x)
- (setq f (match-string 0 x))
- (setq f ""))
- (if (string-match "/" r)
- (concat string (substring r 0 (match-end 0)) f)
- x))
- rtn))
- ((eq flag 'lambda)
- ;; exact match?
- (assoc string thetable)))))
- args)))
+ (let ((thetable collection))
+ (apply #'completing-read
+ prompt
+ (lambda (string predicate &optional flag)
+ (cond
+ ((eq flag nil) (try-completion string thetable))
+ ((eq flag t)
+ (let ((l (length string)))
+ (mapcar (lambda (x)
+ (let ((r (substring x l))
+ (f (if (string-match " ([^)]*)$" x)
+ (match-string 0 x)
+ "")))
+ (if (string-match "/" r)
+ (concat string (substring r 0 (match-end 0)) f)
+ x)))
+ (all-completions string thetable predicate))))
+ ;; Exact match?
+ ((eq flag 'lambda) (assoc string thetable))))
+ args)))
;;;; Dynamic blocks
@@ -11910,19 +12210,12 @@ If not found, stay at current position and return nil."
(setq pos (and (re-search-forward
(concat "^[ \t]*#\\+\\(?:BEGIN\\|begin\\):[ \t]+" name "\\>") nil t)
(match-beginning 0))))
- (if pos (goto-char pos))
+ (when pos (goto-char pos))
pos))
-(defconst org-dblock-start-re
- "^[ \t]*#\\+\\(?:BEGIN\\|begin\\):[ \t]+\\(\\S-+\\)\\([ \t]+\\(.*\\)\\)?"
- "Matches the start line of a dynamic block, with parameters.")
-
-(defconst org-dblock-end-re "^[ \t]*#\\+\\(?:END\\|end\\)\\([: \t\r\n]\\|$\\)"
- "Matches the end of a dynamic block.")
-
(defun org-create-dblock (plist)
"Create a dynamic block section, with parameters taken from PLIST.
-PLIST must contain a :name entry which is used as name of the block."
+PLIST must contain a :name entry which is used as the name of the block."
(when (string-match "\\S-" (buffer-substring (point-at-bol) (point-at-eol)))
(end-of-line 1)
(newline))
@@ -12042,13 +12335,14 @@ This function can be used in a hook."
;;;; Completion
+(declare-function org-export-backend-options "ox" (cl-x) t)
(defun org-get-export-keywords ()
"Return a list of all currently understood export keywords.
Export keywords include options, block names, attributes and
keywords relative to each registered export back-end."
(let (keywords)
(dolist (backend
- (org-bound-and-true-p org-export--registered-backends)
+ (bound-and-true-p org-export-registered-backends)
(delq nil keywords))
;; Back-end name (for keywords, like #+LATEX:)
(push (upcase (symbol-name (org-export-backend-name backend))) keywords)
@@ -12064,27 +12358,24 @@ keywords relative to each registered export back-end."
"TITLE:" "TODO:" "TYP_TODO:" "SELECT_TAGS:" "EXCLUDE_TAGS:"))
(defcustom org-structure-template-alist
- '(("s" "#+BEGIN_SRC ?\n\n#+END_SRC" "\n\n")
- ("e" "#+BEGIN_EXAMPLE\n?\n#+END_EXAMPLE" "\n?\n")
- ("q" "#+BEGIN_QUOTE\n?\n#+END_QUOTE" "\n?\n")
- ("v" "#+BEGIN_VERSE\n?\n#+END_VERSE" "\n?\n")
- ("V" "#+BEGIN_VERBATIM\n?\n#+END_VERBATIM" "\n?\n")
- ("c" "#+BEGIN_CENTER\n?\n#+END_CENTER" "
\n?\n
")
- ("l" "#+BEGIN_LaTeX\n?\n#+END_LaTeX"
- "\n?\n")
- ("L" "#+LaTeX: " "?")
- ("h" "#+BEGIN_HTML\n?\n#+END_HTML"
- "\n?\n")
- ("H" "#+HTML: " "?")
- ("a" "#+BEGIN_ASCII\n?\n#+END_ASCII" "")
- ("A" "#+ASCII: " "")
- ("i" "#+INDEX: ?" "#+INDEX: ?")
- ("I" "#+INCLUDE: %file ?"
- ""))
+ '(("s" "#+BEGIN_SRC ?\n\n#+END_SRC")
+ ("e" "#+BEGIN_EXAMPLE\n?\n#+END_EXAMPLE")
+ ("q" "#+BEGIN_QUOTE\n?\n#+END_QUOTE")
+ ("v" "#+BEGIN_VERSE\n?\n#+END_VERSE")
+ ("V" "#+BEGIN_VERBATIM\n?\n#+END_VERBATIM")
+ ("c" "#+BEGIN_CENTER\n?\n#+END_CENTER")
+ ("l" "#+BEGIN_EXPORT latex\n?\n#+END_EXPORT")
+ ("L" "#+LaTeX: ")
+ ("h" "#+BEGIN_EXPORT html\n?\n#+END_EXPORT")
+ ("H" "#+HTML: ")
+ ("a" "#+BEGIN_EXPORT ascii\n?\n#+END_EXPORT")
+ ("A" "#+ASCII: ")
+ ("i" "#+INDEX: ?")
+ ("I" "#+INCLUDE: %file ?"))
"Structure completion elements.
This is a list of abbreviation keys and values. The value gets inserted
if you type `<' followed by the key and then press the completion key,
-usually `M-TAB'. %file will be replaced by a file name after prompting
+usually `TAB'. %file will be replaced by a file name after prompting
for the file using completion. The cursor will be placed at the position
of the `?' in the template.
There are two templates for each key, the first uses the original Org syntax,
@@ -12095,8 +12386,9 @@ variable `org-mtags-prefer-muse-templates'."
:type '(repeat
(list
(string :tag "Key")
- (string :tag "Template")
- (string :tag "Muse Template"))))
+ (string :tag "Template")))
+ :version "26.1"
+ :package-version '(Org . "8.3"))
(defun org-try-structure-completion ()
"Try to complete a structure template before point.
@@ -12113,29 +12405,28 @@ expands them."
(defun org-complete-expand-structure-template (start cell)
"Expand a structure template."
- (let* ((musep (org-bound-and-true-p org-mtags-prefer-muse-templates))
- (rpl (nth (if musep 2 1) cell))
- (ind ""))
+ (let ((rpl (nth 1 cell))
+ (ind ""))
(delete-region start (point))
- (when (string-match "\\`#\\+" rpl)
+ (when (string-match "\\`[ \t]*#\\+" rpl)
(cond
((bolp))
((not (string-match "\\S-" (buffer-substring (point-at-bol) (point))))
(setq ind (buffer-substring (point-at-bol) (point))))
(t (newline))))
(setq start (point))
- (if (string-match "%file" rpl)
- (setq rpl (replace-match
- (concat
- "\""
- (save-match-data
- (abbreviate-file-name (read-file-name "Include file: ")))
- "\"")
- t t rpl)))
+ (when (string-match "%file" rpl)
+ (setq rpl (replace-match
+ (concat
+ "\""
+ (save-match-data
+ (abbreviate-file-name (read-file-name "Include file: ")))
+ "\"")
+ t t rpl)))
(setq rpl (mapconcat 'identity (split-string rpl "\n")
(concat "\n" ind)))
(insert rpl)
- (if (re-search-backward "\\?" start t) (delete-char 1))))
+ (when (re-search-backward "\\?" start t) (delete-char 1))))
;;;; TODO, DEADLINE, Comments
@@ -12144,17 +12435,18 @@ expands them."
(interactive)
(save-excursion
(org-back-to-heading)
- (let (case-fold-search)
- (cond
- ((looking-at (format org-heading-keyword-regexp-format
- org-comment-string))
- (goto-char (match-end 1))
- (looking-at (concat " +" org-comment-string))
- (replace-match "" t t)
- (when (eolp) (insert " ")))
- ((looking-at org-outline-regexp)
- (goto-char (match-end 0))
- (insert org-comment-string " "))))))
+ (let ((case-fold-search nil))
+ (looking-at org-complex-heading-regexp))
+ (goto-char (or (match-end 3) (match-end 2) (match-end 1)))
+ (skip-chars-forward " \t")
+ (unless (memq (char-before) '(?\s ?\t)) (insert " "))
+ (if (org-in-commented-heading-p t)
+ (delete-region (point)
+ (progn (search-forward " " (line-end-position) 'move)
+ (skip-chars-forward " \t")
+ (point)))
+ (insert org-comment-string)
+ (unless (eolp) (insert " ")))))
(defvar org-last-todo-state-is-todo nil
"This is non-nil when the last TODO state change led to a TODO state.
@@ -12193,43 +12485,65 @@ nil or a string to be used for the todo mark." )
(interactive "P")
(if (eq major-mode 'org-agenda-mode)
(apply 'org-agenda-todo-yesterday arg)
- (let* ((hour (third (decode-time
- (org-current-time))))
+ (let* ((org-use-effective-time t)
+ (hour (nth 2 (decode-time (org-current-time))))
(org-extend-today-until (1+ hour)))
(org-todo arg))))
(defvar org-block-entry-blocking ""
"First entry preventing the TODO state change.")
+(defun org-cancel-repeater ()
+ "Cancel a repeater by setting its numeric value to zero."
+ (interactive)
+ (save-excursion
+ (org-back-to-heading t)
+ (let ((bound1 (point))
+ (bound0 (save-excursion (outline-next-heading) (point))))
+ (when (and (re-search-forward
+ (concat "\\(" org-scheduled-time-regexp "\\)\\|\\("
+ org-deadline-time-regexp "\\)\\|\\("
+ org-ts-regexp "\\)")
+ bound0 t)
+ (re-search-backward "[ \t]+\\(?:[.+]\\)?\\+\\([0-9]+\\)[hdwmy]"
+ bound1 t))
+ (replace-match "0" t nil nil 1)))))
+
+(defvar org-state)
+(defvar org-blocked-by-checkboxes)
(defun org-todo (&optional arg)
"Change the TODO state of an item.
+
The state of an item is given by a keyword at the start of the heading,
like
*** TODO Write paper
*** DONE Call mom
The different keywords are specified in the variable `org-todo-keywords'.
-By default the available states are \"TODO\" and \"DONE\".
-So for this example: when the item starts with TODO, it is changed to DONE.
+By default the available states are \"TODO\" and \"DONE\". So, for this
+example: when the item starts with TODO, it is changed to DONE.
When it starts with DONE, the DONE is removed. And when neither TODO nor
DONE are present, add TODO at the beginning of the heading.
-With \\[universal-argument] prefix arg, use completion to determine the new \
+With `\\[universal-argument]' prefix ARG, use completion to determine the new \
state.
-With numeric prefix arg, switch to that state.
-With a double \\[universal-argument] prefix, switch to the next set of TODO \
+With numeric prefix ARG, switch to that state.
+With a `\\[universal-argument] \\[universal-argument]' prefix, switch to the \
+next set of TODO \
keywords (nextset).
-With a triple \\[universal-argument] prefix, circumvent any state blocking.
+With a `\\[universal-argument] \\[universal-argument] \\[universal-argument]' \
+prefix, circumvent any state blocking.
With a numeric prefix arg of 0, inhibit note taking for the change.
-
-For calling through lisp, arg is also interpreted in the following way:
-`none' -> empty state
-\"\" (empty string) -> switch to empty state
-`done' -> switch to DONE
-`nextset' -> switch to the next set of keywords
-`previousset' -> switch to the previous set of keywords
-\"WAITING\" -> switch to the specified keyword, but only if it
- really is a member of `org-todo-keywords'."
+With a numeric prefix arg of -1, cancel repeater to allow marking as DONE.
+
+When called through ELisp, arg is also interpreted in the following way:
+`none' -> empty state
+\"\" -> switch to empty state
+`done' -> switch to DONE
+`nextset' -> switch to the next set of keywords
+`previousset' -> switch to the previous set of keywords
+\"WAITING\" -> switch to the specified keyword, but only if it
+ really is a member of `org-todo-keywords'."
(interactive "P")
(if (and (org-region-active-p) org-loop-over-headlines-in-active-region)
(let ((cl (if (eq org-loop-over-headlines-in-active-region 'start-level)
@@ -12238,8 +12552,9 @@ For calling through lisp, arg is also interpreted in the following way:
(org-map-entries
`(org-todo ,arg)
org-loop-over-headlines-in-active-region
- cl (if (outline-invisible-p) (org-end-of-subtree nil t))))
- (if (equal arg '(16)) (setq arg 'nextset))
+ cl (when (org-invisible-p) (org-end-of-subtree nil t))))
+ (when (equal arg '(16)) (setq arg 'nextset))
+ (when (equal arg -1) (org-cancel-repeater) (setq arg nil))
(let ((org-blocker-hook org-blocker-hook)
commentp
case-fold-search)
@@ -12252,10 +12567,10 @@ For calling through lisp, arg is also interpreted in the following way:
(save-excursion
(catch 'exit
(org-back-to-heading t)
- (when (looking-at (concat "^\\*+ " org-comment-string))
+ (when (org-in-commented-heading-p t)
(org-toggle-comment)
(setq commentp t))
- (if (looking-at org-outline-regexp) (goto-char (1- (match-end 0))))
+ (when (looking-at org-outline-regexp) (goto-char (1- (match-end 0))))
(or (looking-at (concat " +" org-todo-regexp "\\( +\\|[ \t]*$\\)"))
(looking-at "\\(?: *\\|[ \t]*$\\)"))
(let* ((match-data (match-data))
@@ -12285,31 +12600,30 @@ For calling through lisp, arg is also interpreted in the following way:
(and (not arg) org-use-fast-todo-selection
(not (eq org-use-fast-todo-selection
'prefix)))))
- ;; Use fast selection
+ ;; Use fast selection.
(org-fast-todo-selection))
((and (equal arg '(4))
(or (not org-use-fast-todo-selection)
(not org-todo-key-trigger)))
- ;; Read a state with completion
- (org-icompleting-read
- "State: " (mapcar 'list org-todo-keywords-1)
+ ;; Read a state with completion.
+ (completing-read
+ "State: " (mapcar #'list org-todo-keywords-1)
nil t))
((eq arg 'right)
(if this
(if tail (car tail) nil)
(car org-todo-keywords-1)))
((eq arg 'left)
- (if (equal member org-todo-keywords-1)
- nil
+ (unless (equal member org-todo-keywords-1)
(if this
(nth (- (length org-todo-keywords-1)
(length tail) 2)
org-todo-keywords-1)
(org-last org-todo-keywords-1))))
((and (eq org-use-fast-todo-selection t) (equal arg '(4))
- (setq arg nil))) ; hack to fall back to cycling
+ (setq arg nil))) ;hack to fall back to cycling
(arg
- ;; user or caller requests a specific state
+ ;; User or caller requests a specific state.
(cond
((equal arg "") nil)
((eq arg 'none) nil)
@@ -12327,8 +12641,8 @@ For calling through lisp, arg is also interpreted in the following way:
((nth (1- (prefix-numeric-value arg))
org-todo-keywords-1))))
((null member) (or head (car org-todo-keywords-1)))
- ((equal this final-done-word) nil) ;; -> make empty
- ((null tail) nil) ;; -> first entry
+ ((equal this final-done-word) nil) ;-> make empty
+ ((null tail) nil) ;-> first entry
((memq interpret '(type priority))
(if (eq this-command last-command)
(car tail)
@@ -12346,24 +12660,30 @@ For calling through lisp, arg is also interpreted in the following way:
:position startpos))
dolog now-done-p)
(when org-blocker-hook
- (setq org-last-todo-state-is-todo
- (not (member this org-done-keywords)))
- (unless (save-excursion
- (save-match-data
- (org-with-wide-buffer
- (run-hook-with-args-until-failure
- 'org-blocker-hook change-plist))))
- (if (org-called-interactively-p 'interactive)
- (user-error "TODO state change from %s to %s blocked (by \"%s\")"
- this org-state org-block-entry-blocking)
- ;; fail silently
- (message "TODO state change from %s to %s blocked (by \"%s\")"
- this org-state org-block-entry-blocking)
- (throw 'exit nil))))
+ (let (org-blocked-by-checkboxes block-reason)
+ (setq org-last-todo-state-is-todo
+ (not (member this org-done-keywords)))
+ (unless (save-excursion
+ (save-match-data
+ (org-with-wide-buffer
+ (run-hook-with-args-until-failure
+ 'org-blocker-hook change-plist))))
+ (setq block-reason (if org-blocked-by-checkboxes
+ "contained checkboxes"
+ (format "\"%s\"" org-block-entry-blocking)))
+ (if (called-interactively-p 'interactive)
+ (user-error "TODO state change from %s to %s blocked (by %s)"
+ this org-state block-reason)
+ ;; Fail silently.
+ (message "TODO state change from %s to %s blocked (by %s)"
+ this org-state block-reason)
+ (throw 'exit nil)))))
(store-match-data match-data)
(replace-match next t t)
- (unless (pos-visible-in-window-p hl-pos)
- (message "TODO state changed to %s" (org-trim next)))
+ (cond ((equal this org-state)
+ (message "TODO state was already %s" (org-trim next)))
+ ((pos-visible-in-window-p hl-pos)
+ (message "TODO state changed to %s" (org-trim next))))
(unless head
(setq head (org-get-todo-sequence-head org-state)
ass (assoc head org-todo-kwd-alist)
@@ -12384,11 +12704,11 @@ For calling through lisp, arg is also interpreted in the following way:
(when (and (or org-todo-log-states org-log-done)
(not (eq org-inhibit-logging t))
(not (memq arg '(nextset previousset))))
- ;; we need to look at recording a time and note
+ ;; We need to look at recording a time and note.
(setq dolog (or (nth 1 (assoc org-state org-todo-log-states))
(nth 2 (assoc this org-todo-log-states))))
- (if (and (eq dolog 'note) (eq org-inhibit-logging 'note))
- (setq dolog 'time))
+ (when (and (eq dolog 'note) (eq org-inhibit-logging 'note))
+ (setq dolog 'time))
(when (or (and (not org-state) (not org-closed-keep-when-no-todo))
(and org-state
(member org-state org-not-done-keywords)
@@ -12397,21 +12717,21 @@ For calling through lisp, arg is also interpreted in the following way:
;; If there was a CLOSED time stamp, get rid of it.
(org-add-planning-info nil nil 'closed))
(when (and now-done-p org-log-done)
- ;; It is now done, and it was not done before
+ ;; It is now done, and it was not done before.
(org-add-planning-info 'closed (org-current-effective-time))
- (if (and (not dolog) (eq 'note org-log-done))
- (org-add-log-setup 'done org-state this 'findpos 'note)))
+ (when (and (not dolog) (eq 'note org-log-done))
+ (org-add-log-setup 'done org-state this 'note)))
(when (and org-state dolog)
- ;; This is a non-nil state, and we need to log it
- (org-add-log-setup 'state org-state this 'findpos dolog)))
- ;; Fixup tag positioning
+ ;; This is a non-nil state, and we need to log it.
+ (org-add-log-setup 'state org-state this dolog)))
+ ;; Fixup tag positioning.
(org-todo-trigger-tag-changes org-state)
(and org-auto-align-tags (not org-setting-tags) (org-set-tags nil t))
(when org-provide-todo-statistics
(org-update-parent-todo-statistics))
(run-hooks 'org-after-todo-state-change-hook)
- (if (and arg (not (member org-state org-done-keywords)))
- (setq head (org-get-todo-sequence-head org-state)))
+ (when (and arg (not (member org-state org-done-keywords)))
+ (setq head (org-get-todo-sequence-head org-state)))
(put-text-property (point-at-bol) (point-at-eol) 'org-todo-head head)
;; Do we need to trigger a repeat?
(when now-done-p
@@ -12421,15 +12741,14 @@ For calling through lisp, arg is also interpreted in the following way:
(setq org-agenda-headline-snapshot-before-repeat
(org-get-heading))))
(org-auto-repeat-maybe org-state))
- ;; Fixup cursor location if close to the keyword
- (if (and (outline-on-heading-p)
- (not (bolp))
- (save-excursion (beginning-of-line 1)
- (looking-at org-todo-line-regexp))
- (< (point) (+ 2 (or (match-end 2) (match-end 1)))))
- (progn
- (goto-char (or (match-end 2) (match-end 1)))
- (and (looking-at " ") (just-one-space))))
+ ;; Fixup cursor location if close to the keyword.
+ (when (and (outline-on-heading-p)
+ (not (bolp))
+ (save-excursion (beginning-of-line 1)
+ (looking-at org-todo-line-regexp))
+ (< (point) (+ 2 (or (match-end 2) (match-end 1)))))
+ (goto-char (or (match-end 2) (match-end 1)))
+ (and (looking-at " ") (just-one-space)))
(when org-trigger-hook
(save-excursion
(run-hook-with-args 'org-trigger-hook change-plist)))
@@ -12471,10 +12790,10 @@ changes. Such blocking occurs when:
(> child-level this-level))
;; this todo has children, check whether they are all
;; completed
- (if (and (not (org-entry-is-done-p))
- (org-entry-is-todo-p))
- (progn (setq org-block-entry-blocking (org-get-heading))
- (throw 'dont-block nil)))
+ (when (and (not (org-entry-is-done-p))
+ (org-entry-is-todo-p))
+ (setq org-block-entry-blocking (org-get-heading))
+ (throw 'dont-block nil))
(outline-next-heading)
(setq child-level (funcall outline-level))))))
;; Otherwise, if the task's parent has the :ORDERED: property, and
@@ -12482,8 +12801,9 @@ changes. Such blocking occurs when:
(save-excursion
(org-back-to-heading t)
(let* ((pos (point))
- (parent-pos (and (org-up-heading-safe) (point))))
- (if (not parent-pos) (throw 'dont-block t)) ; no parent
+ (parent-pos (and (org-up-heading-safe) (point)))
+ (case-fold-search nil))
+ (unless parent-pos (throw 'dont-block t)) ; no parent
(when (and (org-not-nil (org-entry-get (point) "ORDERED"))
(forward-line 1)
(re-search-forward org-not-done-heading-regexp pos t))
@@ -12492,11 +12812,11 @@ changes. Such blocking occurs when:
;; Search further up the hierarchy, to see if an ancestor is blocked
(while t
(goto-char parent-pos)
- (if (not (looking-at org-not-done-heading-regexp))
- (throw 'dont-block t)) ; do not block, parent is not a TODO
+ (unless (looking-at org-not-done-heading-regexp)
+ (throw 'dont-block t)) ; do not block, parent is not a TODO
(setq pos (point))
(setq parent-pos (and (org-up-heading-safe) (point)))
- (if (not parent-pos) (throw 'dont-block t)) ; no parent
+ (unless parent-pos (throw 'dont-block t)) ; no parent
(when (and (org-not-nil (org-entry-get (point) "ORDERED"))
(forward-line 1)
(re-search-forward org-not-done-heading-regexp pos t)
@@ -12533,14 +12853,13 @@ See variable `org-track-ordered-property-with-tag'."
(org-back-to-heading)
(if (org-entry-get nil "ORDERED")
(progn
- (org-delete-property "ORDERED" "PROPERTIES")
+ (org-delete-property "ORDERED")
(and tag (org-toggle-tag tag 'off))
(message "Subtasks can be completed in arbitrary order"))
(org-entry-put nil "ORDERED" "t")
(and tag (org-toggle-tag tag 'on))
(message "Subtasks must be completed in sequence")))))
-(defvar org-blocked-by-checkboxes) ; dynamically scoped
(defun org-block-todo-from-checkboxes (change-plist)
"Block turning an entry into a TODO, using checkboxes.
This checks whether the current task should be blocked from state
@@ -12564,32 +12883,32 @@ changes because there are unchecked boxes in this entry."
(outline-next-heading)
(setq end (point))
(goto-char beg)
- (if (org-list-search-forward
- (concat (org-item-beginning-re)
- "\\(?:\\[@\\(?:start:\\)?\\([0-9]+\\|[A-Za-z]\\)\\][ \t]*\\)?"
- "\\[[- ]\\]")
- end t)
- (progn
- (if (boundp 'org-blocked-by-checkboxes)
- (setq org-blocked-by-checkboxes t))
- (throw 'dont-block nil)))))
+ (when (org-list-search-forward
+ (concat (org-item-beginning-re)
+ "\\(?:\\[@\\(?:start:\\)?\\([0-9]+\\|[A-Za-z]\\)\\][ \t]*\\)?"
+ "\\[[- ]\\]")
+ end t)
+ (when (boundp 'org-blocked-by-checkboxes)
+ (setq org-blocked-by-checkboxes t))
+ (throw 'dont-block nil))))
t))) ; do not block
(defun org-entry-blocked-p ()
- "Is the current entry blocked?"
- (org-with-silent-modifications
- (if (org-entry-get nil "NOBLOCKING")
- nil ;; Never block this entry
- (not (run-hook-with-args-until-failure
- 'org-blocker-hook
- (list :type 'todo-state-change
- :position (point)
- :from 'todo
- :to 'done))))))
+ "Non-nil if entry at point is blocked."
+ (and (not (org-entry-get nil "NOBLOCKING"))
+ (member (org-entry-get nil "TODO") org-not-done-keywords)
+ (not (run-hook-with-args-until-failure
+ 'org-blocker-hook
+ (list :type 'todo-state-change
+ :position (point)
+ :from 'todo
+ :to 'done)))))
(defun org-update-statistics-cookies (all)
"Update the statistics cookie, either from TODO or from checkboxes.
-This should be called with the cursor in a line with a statistics cookie."
+This should be called with the cursor in a line with a statistics
+cookie. When called with a \\[universal-argument] prefix, update
+all statistics cookies in the buffer."
(interactive "P")
(if all
(progn
@@ -12605,7 +12924,7 @@ This should be called with the cursor in a line with a statistics cookie."
(setq l1 (org-outline-level))
(setq end (save-excursion
(outline-next-heading)
- (if (org-at-heading-p) (setq l2 (org-outline-level)))
+ (when (org-at-heading-p) (setq l2 (org-outline-level)))
(point)))
(if (and (save-excursion
(re-search-forward
@@ -12642,7 +12961,7 @@ statistics everywhere."
(box-re "\\(\\(\\[[0-9]*%\\]\\)\\|\\(\\[[0-9]*/[0-9]*\\]\\)\\)")
level ltoggle l1 new ndel
(cnt-all 0) (cnt-done 0) is-percent kwd
- checkbox-beg ov ovs ove cookie-present)
+ checkbox-beg cookie-present)
(catch 'exit
(save-excursion
(beginning-of-line 1)
@@ -12677,14 +12996,31 @@ statistics everywhere."
(setq kwd (and (or recursive (= l1 ltoggle))
(match-string 2)))
(if (or (eq org-provide-todo-statistics 'all-headlines)
+ (and (eq org-provide-todo-statistics t)
+ (or (member kwd org-done-keywords)))
(and (listp org-provide-todo-statistics)
+ (stringp (car org-provide-todo-statistics))
(or (member kwd org-provide-todo-statistics)
- (member kwd org-done-keywords))))
+ (member kwd org-done-keywords)))
+ (and (listp org-provide-todo-statistics)
+ (listp (car org-provide-todo-statistics))
+ (or (member kwd (car org-provide-todo-statistics))
+ (and (member kwd org-done-keywords)
+ (member kwd (cadr org-provide-todo-statistics))))))
(setq cnt-all (1+ cnt-all))
- (if (eq org-provide-todo-statistics t)
- (and kwd (setq cnt-all (1+ cnt-all)))))
- (and (member kwd org-done-keywords)
- (setq cnt-done (1+ cnt-done)))
+ (and (eq org-provide-todo-statistics t)
+ kwd
+ (setq cnt-all (1+ cnt-all))))
+ (when (or (and (member org-provide-todo-statistics '(t all-headlines))
+ (member kwd org-done-keywords))
+ (and (listp org-provide-todo-statistics)
+ (listp (car org-provide-todo-statistics))
+ (member kwd org-done-keywords)
+ (member kwd (cadr org-provide-todo-statistics)))
+ (and (listp org-provide-todo-statistics)
+ (stringp (car org-provide-todo-statistics))
+ (member kwd org-done-keywords)))
+ (setq cnt-done (1+ cnt-done)))
(outline-next-heading)))
(setq new
(if is-percent
@@ -12692,15 +13028,10 @@ statistics everywhere."
(max 1 cnt-all)))
(format "[%d/%d]" cnt-done cnt-all))
ndel (- (match-end 0) checkbox-beg))
- ;; handle overlays when updating cookie from column view
- (when (setq ov (car (overlays-at checkbox-beg)))
- (setq ovs (overlay-start ov) ove (overlay-end ov))
- (delete-overlay ov))
(goto-char checkbox-beg)
(insert new)
(delete-region (point) (+ (point) ndel))
- (when org-auto-align-tags (org-fix-tags-on-the-fly))
- (when ov (move-overlay ov ovs ove)))
+ (when org-auto-align-tags (org-fix-tags-on-the-fly)))
(when cookie-present
(run-hook-with-args 'org-after-todo-statistics-hook
cnt-done (- cnt-all cnt-done))))))
@@ -12736,9 +13067,9 @@ This hook runs even if there is no statistics cookie present, in which case
(when (and (stringp state) (> (length state) 0))
(setq changes (append changes (cdr (assoc state l)))))
(when (member state org-not-done-keywords)
- (setq changes (append changes (cdr (assoc 'todo l)))))
+ (setq changes (append changes (cdr (assq 'todo l)))))
(when (member state org-done-keywords)
- (setq changes (append changes (cdr (assoc 'done l)))))
+ (setq changes (append changes (cdr (assq 'done l)))))
(dolist (c changes)
(org-toggle-tag (car c) (if (cdr c) 'on 'off)))))
@@ -12749,7 +13080,7 @@ This hook runs even if there is no statistics cookie present, in which case
org-log-repeat nil
org-todo-log-states nil)
(dolist (w (org-split-string value))
- (let* (a)
+ (let (a)
(cond
((setq a (assoc w org-startup-options))
(and (member (nth 1 a) '(org-log-done org-log-repeat))
@@ -12786,7 +13117,7 @@ Returns the new TODO keyword, or nil if no state change should occur."
(expert nil)
(fwidth (+ maxlen 3 1 3))
(ncol (/ (- (window-width) 4) fwidth))
- tg cnt c tbl
+ tg cnt e c tbl
groups ingroup)
(save-excursion
(save-window-excursion
@@ -12794,13 +13125,13 @@ Returns the new TODO keyword, or nil if no state change should occur."
(set-buffer (get-buffer-create " *Org todo*"))
(org-switch-to-buffer-other-window (get-buffer-create " *Org todo*")))
(erase-buffer)
- (org-set-local 'org-done-keywords done-keywords)
+ (setq-local org-done-keywords done-keywords)
(setq tbl fulltable cnt 0)
- (dolist (e tbl)
+ (while (setq e (pop tbl))
(cond
((equal e '(:startgroup))
(push '() groups) (setq ingroup t)
- (when (not (= cnt 0))
+ (unless (= cnt 0)
(setq cnt 0)
(insert "\n"))
(insert "{ "))
@@ -12808,7 +13139,7 @@ Returns the new TODO keyword, or nil if no state change should occur."
(setq ingroup nil cnt 0)
(insert "}\n"))
((equal e '(:newline))
- (when (not (= cnt 0))
+ (unless (= cnt 0)
(setq cnt 0)
(insert "\n")
(setq e (car tbl))
@@ -12817,19 +13148,19 @@ Returns the new TODO keyword, or nil if no state change should occur."
(setq tbl (cdr tbl)))))
(t
(setq tg (car e) c (cdr e))
- (if ingroup (push tg (car groups)))
+ (when ingroup (push tg (car groups)))
(setq tg (org-add-props tg nil 'face
(org-get-todo-face tg)))
- (if (and (= cnt 0) (not ingroup)) (insert " "))
+ (when (and (= cnt 0) (not ingroup)) (insert " "))
(insert "[" c "] " tg (make-string
(- fwidth 4 (length tg)) ?\ ))
(when (= (setq cnt (1+ cnt)) ncol)
(insert "\n")
- (if ingroup (insert " "))
+ (when ingroup (insert " "))
(setq cnt 0)))))
(insert "\n")
(goto-char (point-min))
- (if (not expert) (org-fit-window-to-buffer))
+ (unless expert (org-fit-window-to-buffer))
(message "[a-z..]:Set [SPC]:clear")
(setq c (let ((inhibit-quit t)) (read-char-exclusive)))
(cond
@@ -12851,12 +13182,19 @@ Returns the new TODO keyword, or nil if no state change should occur."
"Return the TODO keyword of the current subtree."
(save-excursion
(org-back-to-heading t)
- (and (looking-at org-todo-line-regexp)
+ (and (let ((case-fold-search nil)) (looking-at org-todo-line-regexp))
(match-end 2)
(match-string 2))))
(defun org-at-date-range-p (&optional inactive-ok)
- "Is the cursor inside a date range?"
+ "Non-nil if point is inside a date range.
+
+When optional argument INACTIVE-OK is non-nil, also consider
+inactive time ranges.
+
+When this function returns a non-nil value, match data is set
+according to `org-tr-regexp-both' or `org-tr-regexp', depending
+on INACTIVE-OK."
(interactive)
(save-excursion
(catch 'exit
@@ -12888,14 +13226,15 @@ Returns the new TODO keyword, or nil if no state change should occur."
(defvar org-last-inserted-timestamp)
(defvar org-log-post-message)
(defvar org-log-note-purpose)
-(defvar org-log-note-how)
+(defvar org-log-note-how nil)
(defvar org-log-note-extra)
(defun org-auto-repeat-maybe (done-word)
- "Check if the current headline contains a repeated deadline/schedule.
+ "Check if the current headline contains a repeated time-stamp.
+
If yes, set TODO state back to what it was and change the base date
of repeating deadline/scheduled time stamps to new date.
+
This function is run automatically after each state change to a DONE state."
- ;; last-state is dynamically scoped into this function
(let* ((repeat (org-get-repeat))
(aa (assoc org-last-state org-todo-kwd-alist))
(interpret (nth 1 aa))
@@ -12903,73 +13242,108 @@ This function is run automatically after each state change to a DONE state."
(whata '(("h" . hour) ("d" . day) ("m" . month) ("y" . year)))
(msg "Entry repeats: ")
(org-log-done nil)
- (org-todo-log-states nil)
- re type n what ts time to-state)
- (when repeat
- (if (eq org-log-repeat t) (setq org-log-repeat 'state))
- (setq to-state (or (org-entry-get nil "REPEAT_TO_STATE")
- org-todo-repeat-to-state))
- (unless (and to-state (member to-state org-todo-keywords-1))
- (setq to-state (if (eq interpret 'type) org-last-state head)))
- (org-todo to-state)
+ (org-todo-log-states nil))
+ (when (and repeat (not (zerop (string-to-number (substring repeat 1)))))
+ (when (eq org-log-repeat t) (setq org-log-repeat 'state))
+ (let ((to-state (or (org-entry-get nil "REPEAT_TO_STATE" 'selective)
+ org-todo-repeat-to-state)))
+ (org-todo (cond ((and to-state (member to-state org-todo-keywords-1))
+ to-state)
+ ((eq interpret 'type) org-last-state)
+ (head)
+ (t 'none))))
(when (or org-log-repeat (org-entry-get nil "CLOCK"))
(org-entry-put nil "LAST_REPEAT" (format-time-string
(org-time-stamp-format t t))))
(when org-log-repeat
(if (or (memq 'org-add-log-note (default-value 'post-command-hook))
(memq 'org-add-log-note post-command-hook))
- ;; OK, we are already setup for some record
- (if (eq org-log-repeat 'note)
- ;; make sure we take a note, not only a time stamp
- (setq org-log-note-how 'note))
- ;; Set up for taking a record
- (org-add-log-setup 'state (or done-word (car org-done-keywords))
+ ;; We are already setup for some record.
+ (when (eq org-log-repeat 'note)
+ ;; Make sure we take a note, not only a time stamp.
+ (setq org-log-note-how 'note))
+ ;; Set up for taking a record.
+ (org-add-log-setup 'state
+ (or done-word (car org-done-keywords))
org-last-state
- 'findpos org-log-repeat)))
+ org-log-repeat)))
(org-back-to-heading t)
(org-add-planning-info nil nil 'closed)
- (setq re (concat "\\(" org-scheduled-time-regexp "\\)\\|\\("
- org-deadline-time-regexp "\\)\\|\\("
- org-ts-regexp "\\)"))
- (while (re-search-forward
- re (save-excursion (outline-next-heading) (point)) t)
- (setq type (if (match-end 1) org-scheduled-string
- (if (match-end 3) org-deadline-string "Plain:"))
- ts (match-string (if (match-end 2) 2 (if (match-end 4) 4 0))))
- (when (string-match "\\([.+]\\)?\\(\\+[0-9]+\\)\\([hdwmy]\\)" ts)
- (setq n (string-to-number (match-string 2 ts))
- what (match-string 3 ts))
- (if (equal what "w") (setq n (* n 7) what "d"))
- (if (and (equal what "h") (not (string-match "[0-9]\\{1,2\\}:[0-9]\\{2\\}" ts)))
- (user-error "Cannot repeat in Repeat in %d hour(s) because no hour has been set" n))
- ;; Preparation, see if we need to modify the start date for the change
- (when (match-end 1)
- (setq time (save-match-data (org-time-string-to-time ts)))
+ (let ((end (save-excursion (outline-next-heading) (point)))
+ (planning-re (regexp-opt
+ (list org-scheduled-string org-deadline-string))))
+ (while (re-search-forward org-ts-regexp end t)
+ (let* ((ts (match-string 0))
+ (planning? (org-at-planning-p))
+ (type (if (not planning?) "Plain:"
+ (save-excursion
+ (re-search-backward
+ planning-re (line-beginning-position) t)
+ (match-string 0)))))
(cond
- ((equal (match-string 1 ts) ".")
- ;; Shift starting date to today
- (org-timestamp-change
- (- (org-today) (time-to-days time))
- 'day))
- ((equal (match-string 1 ts) "+")
- (let ((nshiftmax 10) (nshift 0))
- (while (or (= nshift 0)
- (<= (time-to-days time)
- (time-to-days (current-time))))
- (when (= (incf nshift) nshiftmax)
- (or (y-or-n-p (message "%d repeater intervals were not enough to shift date past today. Continue? " nshift))
- (error "Abort")))
- (org-timestamp-change n (cdr (assoc what whata)))
- (org-at-timestamp-p t)
- (setq ts (match-string 1))
- (setq time (save-match-data (org-time-string-to-time ts)))))
- (org-timestamp-change (- n) (cdr (assoc what whata)))
- ;; rematch, so that we have everything in place for the real shift
- (org-at-timestamp-p t)
- (setq ts (match-string 1))
- (string-match "\\([.+]\\)?\\(\\+[0-9]+\\)\\([hdwmy]\\)" ts))))
- (save-excursion (org-timestamp-change n (cdr (assoc what whata)) nil t))
- (setq msg (concat msg type " " org-last-changed-timestamp " "))))
+ ;; Ignore fake time-stamps (e.g., within comments).
+ ((and (not planning?)
+ (not (org-at-property-p))
+ (not (eq 'timestamp
+ (org-element-type (save-excursion
+ (backward-char)
+ (org-element-context)))))))
+ ;; Time-stamps without a repeater are usually skipped.
+ ;; However, a SCHEDULED time-stamp without one is
+ ;; removed, as it is considered as no longer relevant.
+ ((not (string-match "\\([.+]\\)?\\(\\+[0-9]+\\)\\([hdwmy]\\)" ts))
+ (when (equal type org-scheduled-string)
+ (org-remove-timestamp-with-keyword type)))
+ (t
+ (let ((n (string-to-number (match-string 2 ts)))
+ (what (match-string 3 ts)))
+ (when (equal what "w") (setq n (* n 7) what "d"))
+ (when (and (equal what "h")
+ (not (string-match-p "[0-9]\\{1,2\\}:[0-9]\\{2\\}"
+ ts)))
+ (user-error
+ "Cannot repeat in Repeat in %d hour(s) because no hour \
+has been set"
+ n))
+ ;; Preparation, see if we need to modify the start
+ ;; date for the change.
+ (when (match-end 1)
+ (let ((time (save-match-data (org-time-string-to-time ts))))
+ (cond
+ ((equal (match-string 1 ts) ".")
+ ;; Shift starting date to today
+ (org-timestamp-change
+ (- (org-today) (time-to-days time))
+ 'day))
+ ((equal (match-string 1 ts) "+")
+ (let ((nshiftmax 10)
+ (nshift 0))
+ (while (or (= nshift 0)
+ (not (time-less-p (current-time) time)))
+ (when (= (cl-incf nshift) nshiftmax)
+ (or (y-or-n-p
+ (format "%d repeater intervals were not \
+enough to shift date past today. Continue? "
+ nshift))
+ (user-error "Abort")))
+ (org-timestamp-change n (cdr (assoc what whata)))
+ (org-at-timestamp-p t)
+ (setq ts (match-string 1))
+ (setq time
+ (save-match-data
+ (org-time-string-to-time ts)))))
+ (org-timestamp-change (- n) (cdr (assoc what whata)))
+ ;; Rematch, so that we have everything in place
+ ;; for the real shift.
+ (org-at-timestamp-p t)
+ (setq ts (match-string 1))
+ (string-match "\\([.+]\\)?\\(\\+[0-9]+\\)\\([hdwmy]\\)"
+ ts)))))
+ (save-excursion
+ (org-timestamp-change n (cdr (assoc what whata)) nil t))
+ (setq msg
+ (concat
+ msg type " " org-last-changed-timestamp " "))))))))
(setq org-log-post-message msg)
(message "%s" msg))))
@@ -12977,7 +13351,7 @@ This function is run automatically after each state change to a DONE state."
"Make a compact tree which shows all headlines marked with TODO.
The tree will show the lines where the regexp matches, and all higher
headlines above the match.
-With a \\[universal-argument] prefix, prompt for a regexp to match.
+With a `\\[universal-argument]' prefix, prompt for a regexp to match.
With a numeric prefix N, construct a sparse tree for the Nth element
of `org-todo-keywords-1'."
(interactive "P")
@@ -12985,8 +13359,9 @@ of `org-todo-keywords-1'."
(kwd-re
(cond ((null arg) org-not-done-regexp)
((equal arg '(4))
- (let ((kwd (org-icompleting-read "Keyword (or KWD1|KWD2|...): "
- (mapcar 'list org-todo-keywords-1))))
+ (let ((kwd
+ (completing-read "Keyword (or KWD1|KWD2|...): "
+ (mapcar #'list org-todo-keywords-1))))
(concat "\\("
(mapconcat 'identity (org-split-string kwd "|") "\\|")
"\\)\\>")))
@@ -12997,75 +13372,99 @@ of `org-todo-keywords-1'."
(message "%d TODO entries found"
(org-occur (concat "^" org-outline-regexp " *" kwd-re )))))
+(defun org--deadline-or-schedule (arg type time)
+ "Insert DEADLINE or SCHEDULE information in current entry.
+TYPE is either `deadline' or `scheduled'. See `org-deadline' or
+`org-schedule' for information about ARG and TIME arguments."
+ (let* ((deadline? (eq type 'deadline))
+ (keyword (if deadline? org-deadline-string org-scheduled-string))
+ (log (if deadline? org-log-redeadline org-log-reschedule))
+ (old-date (org-entry-get nil (if deadline? "DEADLINE" "SCHEDULED")))
+ (old-date-time (and old-date (org-time-string-to-time old-date)))
+ ;; Save repeater cookie from either TIME or current scheduled
+ ;; time stamp. We are going to insert it back at the end of
+ ;; the process.
+ (repeater (or (and (org-string-nw-p time)
+ ;; We use `org-repeat-re' because we need
+ ;; to tell the difference between a real
+ ;; repeater and a time delta, e.g. "+2d".
+ (string-match org-repeat-re time)
+ (match-string 1 time))
+ (and (org-string-nw-p old-date)
+ (string-match "\\([.+-]+[0-9]+[hdwmy]\
+\\(?:[/ ][-+]?[0-9]+[hdwmy]\\)?\\)"
+ old-date)
+ (match-string 1 old-date)))))
+ (pcase arg
+ (`(4)
+ (when (and old-date log)
+ (org-add-log-setup (if deadline? 'deldeadline 'delschedule)
+ nil old-date log))
+ (org-remove-timestamp-with-keyword keyword)
+ (message (if deadline? "Item no longer has a deadline."
+ "Item is no longer scheduled.")))
+ (`(16)
+ (save-excursion
+ (org-back-to-heading t)
+ (let ((regexp (if deadline? org-deadline-time-regexp
+ org-scheduled-time-regexp)))
+ (if (not (re-search-forward regexp (line-end-position 2) t))
+ (user-error (if deadline? "No deadline information to update"
+ "No scheduled information to update"))
+ (let* ((rpl0 (match-string 1))
+ (rpl (replace-regexp-in-string " -[0-9]+[hdwmy]" "" rpl0))
+ (msg (if deadline? "Warn starting from" "Delay until")))
+ (replace-match
+ (concat keyword
+ " <" rpl
+ (format " -%dd"
+ (abs (- (time-to-days
+ (save-match-data
+ (org-read-date
+ nil t nil msg old-date-time)))
+ (time-to-days old-date-time))))
+ ">") t t))))))
+ (_
+ (org-add-planning-info type time 'closed)
+ (when (and old-date
+ log
+ (not (equal old-date org-last-inserted-timestamp)))
+ (org-add-log-setup (if deadline? 'redeadline 'reschedule)
+ org-last-inserted-timestamp
+ old-date
+ log))
+ (when repeater
+ (save-excursion
+ (org-back-to-heading t)
+ (when (re-search-forward
+ (concat keyword " " org-last-inserted-timestamp)
+ (line-end-position 2)
+ t)
+ (goto-char (1- (match-end 0)))
+ (insert " " repeater)
+ (setq org-last-inserted-timestamp
+ (concat (substring org-last-inserted-timestamp 0 -1)
+ " " repeater
+ (substring org-last-inserted-timestamp -1))))))
+ (message (if deadline? "Deadline on %s" "Scheduled to %s")
+ org-last-inserted-timestamp)))))
+
(defun org-deadline (arg &optional time)
"Insert the \"DEADLINE:\" string with a timestamp to make a deadline.
With one universal prefix argument, remove any deadline from the item.
With two universal prefix arguments, prompt for a warning delay.
With argument TIME, set the deadline at the corresponding date. TIME
-can either be an Org date like \"2011-07-24\" or a delta like \"+2d\"."
- (interactive "P")
- (if (and (org-region-active-p) org-loop-over-headlines-in-active-region)
- (let ((cl (if (eq org-loop-over-headlines-in-active-region 'start-level)
- 'region-start-level 'region))
- org-loop-over-headlines-in-active-region)
- (org-map-entries
- `(org-deadline ',arg ,time)
- org-loop-over-headlines-in-active-region
- cl (if (outline-invisible-p) (org-end-of-subtree nil t))))
- (let* ((old-date (org-entry-get nil "DEADLINE"))
- (old-date-time (if old-date (org-time-string-to-time old-date)))
- (repeater (and old-date
- (string-match
- "\\([.+-]+[0-9]+[hdwmy]\\(?:[/ ][-+]?[0-9]+[hdwmy]\\)?\\) ?"
- old-date)
- (match-string 1 old-date))))
- (cond
- ((equal arg '(4))
- (when (and old-date org-log-redeadline)
- (org-add-log-setup 'deldeadline nil old-date 'findpos
- org-log-redeadline))
- (org-remove-timestamp-with-keyword org-deadline-string)
- (message "Item no longer has a deadline."))
- ((equal arg '(16))
- (save-excursion
- (org-back-to-heading t)
- (if (re-search-forward
- org-deadline-time-regexp
- (save-excursion (outline-next-heading) (point)) t)
- (let* ((rpl0 (match-string 1))
- (rpl (replace-regexp-in-string " -[0-9]+[hdwmy]" "" rpl0)))
- (replace-match
- (concat org-deadline-string
- " <" rpl
- (format " -%dd"
- (abs
- (- (time-to-days
- (save-match-data
- (org-read-date nil t nil "Warn starting from" old-date-time)))
- (time-to-days old-date-time))))
- ">") t t))
- (user-error "No deadline information to update"))))
- (t
- (org-add-planning-info 'deadline time 'closed)
- (when (and old-date org-log-redeadline
- (not (equal old-date
- (substring org-last-inserted-timestamp 1 -1))))
- (org-add-log-setup 'redeadline nil old-date 'findpos
- org-log-redeadline))
- (when repeater
- (save-excursion
- (org-back-to-heading t)
- (when (re-search-forward (concat org-deadline-string " "
- org-last-inserted-timestamp)
- (save-excursion
- (outline-next-heading) (point)) t)
- (goto-char (1- (match-end 0)))
- (insert " " repeater)
- (setq org-last-inserted-timestamp
- (concat (substring org-last-inserted-timestamp 0 -1)
- " " repeater
- (substring org-last-inserted-timestamp -1))))))
- (message "Deadline on %s" org-last-inserted-timestamp))))))
+can either be an Org date like \"2011-07-24\" or a delta like \"+2d\"."
+ (interactive "P")
+ (if (and (org-region-active-p) org-loop-over-headlines-in-active-region)
+ (org-map-entries
+ (lambda () (org--deadline-or-schedule arg 'deadline time))
+ nil
+ (if (eq org-loop-over-headlines-in-active-region 'start-level)
+ 'region-start-level
+ 'region)
+ (lambda () (when (org-invisible-p) (org-end-of-subtree nil t))))
+ (org--deadline-or-schedule arg 'deadline time)))
(defun org-schedule (arg &optional time)
"Insert the SCHEDULED: string with a timestamp to schedule a TODO item.
@@ -13075,68 +13474,14 @@ With argument TIME, scheduled at the corresponding date. TIME can
either be an Org date like \"2011-07-24\" or a delta like \"+2d\"."
(interactive "P")
(if (and (org-region-active-p) org-loop-over-headlines-in-active-region)
- (let ((cl (if (eq org-loop-over-headlines-in-active-region 'start-level)
- 'region-start-level 'region))
- org-loop-over-headlines-in-active-region)
- (org-map-entries
- `(org-schedule ',arg ,time)
- org-loop-over-headlines-in-active-region
- cl (if (outline-invisible-p) (org-end-of-subtree nil t))))
- (let* ((old-date (org-entry-get nil "SCHEDULED"))
- (old-date-time (if old-date (org-time-string-to-time old-date)))
- (repeater (and old-date
- (string-match
- "\\([.+-]+[0-9]+[hdwmy]\\(?:[/ ][-+]?[0-9]+[hdwmy]\\)?\\) ?"
- old-date)
- (match-string 1 old-date))))
- (cond
- ((equal arg '(4))
- (progn
- (when (and old-date org-log-reschedule)
- (org-add-log-setup 'delschedule nil old-date 'findpos
- org-log-reschedule))
- (org-remove-timestamp-with-keyword org-scheduled-string)
- (message "Item is no longer scheduled.")))
- ((equal arg '(16))
- (save-excursion
- (org-back-to-heading t)
- (if (re-search-forward
- org-scheduled-time-regexp
- (save-excursion (outline-next-heading) (point)) t)
- (let* ((rpl0 (match-string 1))
- (rpl (replace-regexp-in-string " -[0-9]+[hdwmy]" "" rpl0)))
- (replace-match
- (concat org-scheduled-string
- " <" rpl
- (format " -%dd"
- (abs
- (- (time-to-days
- (save-match-data
- (org-read-date nil t nil "Delay until" old-date-time)))
- (time-to-days old-date-time))))
- ">") t t))
- (user-error "No scheduled information to update"))))
- (t
- (org-add-planning-info 'scheduled time 'closed)
- (when (and old-date org-log-reschedule
- (not (equal old-date
- (substring org-last-inserted-timestamp 1 -1))))
- (org-add-log-setup 'reschedule nil old-date 'findpos
- org-log-reschedule))
- (when repeater
- (save-excursion
- (org-back-to-heading t)
- (when (re-search-forward (concat org-scheduled-string " "
- org-last-inserted-timestamp)
- (save-excursion
- (outline-next-heading) (point)) t)
- (goto-char (1- (match-end 0)))
- (insert " " repeater)
- (setq org-last-inserted-timestamp
- (concat (substring org-last-inserted-timestamp 0 -1)
- " " repeater
- (substring org-last-inserted-timestamp -1))))))
- (message "Scheduled to %s" org-last-inserted-timestamp))))))
+ (org-map-entries
+ (lambda () (org--deadline-or-schedule arg 'scheduled time))
+ nil
+ (if (eq org-loop-over-headlines-in-active-region 'start-level)
+ 'region-start-level
+ 'region)
+ (lambda () (when (org-invisible-p) (org-end-of-subtree nil t))))
+ (org--deadline-or-schedule arg 'scheduled time)))
(defun org-get-scheduled-time (pom &optional inherit)
"Get the scheduled time as a time tuple, of a format suitable
@@ -13167,24 +13512,36 @@ nil."
(if (and (string-match "\\S-" (buffer-substring (point-at-bol) (point)))
(equal (char-before) ?\ ))
(backward-delete-char 1)
- (if (string-match "^[ \t]*$" (buffer-substring
- (point-at-bol) (point-at-eol)))
- (delete-region (point-at-bol)
- (min (point-max) (1+ (point-at-eol))))))))))
+ (when (string-match "^[ \t]*$" (buffer-substring
+ (point-at-bol) (point-at-eol)))
+ (delete-region (point-at-bol)
+ (min (point-max) (1+ (point-at-eol))))))))))
(defvar org-time-was-given) ; dynamically scoped parameter
(defvar org-end-time-was-given) ; dynamically scoped parameter
-(defun org-add-planning-info (what &optional time &rest remove)
- "Insert new timestamp with keyword in the line directly after the headline.
-WHAT indicates what kind of time stamp to add. TIME indicates the time to use.
-If non is given, the user is prompted for a date.
-REMOVE indicates what kind of entries to remove. An old WHAT entry will also
-be removed."
- (interactive)
- (let (org-time-was-given org-end-time-was-given ts
- end default-time default-input)
+(defun org-at-planning-p ()
+ "Non-nil when point is on a planning info line."
+ ;; This is as accurate and faster than `org-element-at-point' since
+ ;; planning info location is fixed in the section.
+ (org-with-wide-buffer
+ (beginning-of-line)
+ (and (looking-at-p org-planning-line-re)
+ (eq (point)
+ (ignore-errors
+ (if (and (featurep 'org-inlinetask) (org-inlinetask-in-task-p))
+ (org-back-to-heading t)
+ (org-with-limited-levels (org-back-to-heading t)))
+ (line-beginning-position 2))))))
+(defun org-add-planning-info (what &optional time &rest remove)
+ "Insert new timestamp with keyword in the planning line.
+WHAT indicates what kind of time stamp to add. It is a symbol
+among `closed', `deadline', `scheduled' and nil. TIME indicates
+the time to use. If none is given, the user is prompted for
+a date. REMOVE indicates what kind of entries to remove. An old
+WHAT entry will also be removed."
+ (let (org-time-was-given org-end-time-was-given default-time default-input)
(catch 'exit
(when (and (memq what '(scheduled deadline))
(or (not time)
@@ -13193,108 +13550,98 @@ be removed."
;; Try to get a default date/time from existing timestamp
(save-excursion
(org-back-to-heading t)
- (setq end (save-excursion (outline-next-heading) (point)))
- (when (re-search-forward (if (eq what 'scheduled)
- org-scheduled-time-regexp
- org-deadline-time-regexp)
- end t)
- (setq ts (match-string 1)
- default-time
- (apply 'encode-time (org-parse-time-string ts))
- default-input (and ts (org-get-compact-tod ts))))))
+ (let ((end (save-excursion (outline-next-heading) (point))) ts)
+ (when (re-search-forward (if (eq what 'scheduled)
+ org-scheduled-time-regexp
+ org-deadline-time-regexp)
+ end t)
+ (setq ts (match-string 1)
+ default-time (apply 'encode-time (org-parse-time-string ts))
+ default-input (and ts (org-get-compact-tod ts)))))))
(when what
(setq time
(if (stringp time)
- ;; This is a string (relative or absolute), set proper date
- (apply 'encode-time
+ ;; This is a string (relative or absolute), set
+ ;; proper date.
+ (apply #'encode-time
(org-read-date-analyze
time default-time (decode-time default-time)))
;; If necessary, get the time from the user
(or time (org-read-date nil 'to-time nil nil
default-time default-input)))))
- (when (and org-insert-labeled-timestamps-at-point
- (member what '(scheduled deadline)))
- (insert
- (if (eq what 'scheduled) org-scheduled-string org-deadline-string) " ")
- (org-insert-time-stamp time org-time-was-given
- nil nil nil (list org-end-time-was-given))
- (setq what nil))
- (save-excursion
- (save-restriction
- (let (col list elt ts buffer-invisibility-spec)
- (org-back-to-heading t)
- (looking-at (concat org-outline-regexp "\\( *\\)[^\r\n]*"))
- (goto-char (match-end 1))
- (setq col (current-column))
- (goto-char (match-end 0))
- (if (eobp) (insert "\n") (forward-char 1))
- (when (and (not what)
- (not (looking-at
- (concat "[ \t]*"
- org-keyword-time-not-clock-regexp))))
- ;; Nothing to add, nothing to remove...... :-)
- (throw 'exit nil))
- (if (and (not (looking-at org-outline-regexp))
- (looking-at (concat "[^\r\n]*?" org-keyword-time-regexp
- "[^\r\n]*"))
- (not (equal (match-string 1) org-clock-string)))
- (narrow-to-region (match-beginning 0) (match-end 0))
- (insert-before-markers "\n")
- (backward-char 1)
- (narrow-to-region (point) (point))
- (and org-adapt-indentation (org-indent-to-column col)))
- ;; Check if we have to remove something.
- (setq list (cons what remove))
- (while list
- (setq elt (pop list))
- (when (or (and (eq elt 'scheduled)
- (re-search-forward org-scheduled-time-regexp nil t))
- (and (eq elt 'deadline)
- (re-search-forward org-deadline-time-regexp nil t))
- (and (eq elt 'closed)
- (re-search-forward org-closed-time-regexp nil t)))
- (replace-match "")
- (if (looking-at "--+<[^>]+>") (replace-match ""))))
- (and (looking-at "[ \t]+") (replace-match ""))
- (and org-adapt-indentation (bolp) (org-indent-to-column col))
- (when what
- (insert
- (if (not (or (bolp) (eq (char-before) ?\ ))) " " "")
- (cond ((eq what 'scheduled) org-scheduled-string)
- ((eq what 'deadline) org-deadline-string)
- ((eq what 'closed) org-closed-string))
- " ")
- (setq ts (org-insert-time-stamp
- time
- (or org-time-was-given
- (and (eq what 'closed) org-log-done-with-time))
- (eq what 'closed)
- nil nil (list org-end-time-was-given)))
- (insert
- (if (not (or (bolp) (eq (char-before) ?\ )
- (memq (char-after) '(32 10))
- (eobp))) " " ""))
- (end-of-line 1))
- (goto-char (point-min))
- (widen)
- (if (and (looking-at "[ \t]*\n")
- (equal (char-before) ?\n))
- (delete-region (1- (point)) (point-at-eol)))
- ts))))))
-
-(defvar org-log-note-marker (make-marker))
+ (org-with-wide-buffer
+ (org-back-to-heading t)
+ (forward-line)
+ (unless (bolp) (insert "\n"))
+ (cond ((looking-at-p org-planning-line-re)
+ ;; Move to current indentation.
+ (skip-chars-forward " \t")
+ ;; Check if we have to remove something.
+ (dolist (type (if what (cons what remove) remove))
+ (save-excursion
+ (when (re-search-forward
+ (cl-case type
+ (closed org-closed-time-regexp)
+ (deadline org-deadline-time-regexp)
+ (scheduled org-scheduled-time-regexp)
+ (otherwise
+ (error "Invalid planning type: %s" type)))
+ (line-end-position) t)
+ ;; Delete until next keyword or end of line.
+ (delete-region
+ (match-beginning 0)
+ (if (re-search-forward org-keyword-time-not-clock-regexp
+ (line-end-position)
+ t)
+ (match-beginning 0)
+ (line-end-position))))))
+ ;; If there is nothing more to add and no more keyword
+ ;; is left, remove the line completely.
+ (if (and (looking-at-p "[ \t]*$") (not what))
+ (delete-region (line-beginning-position)
+ (line-beginning-position 2))
+ ;; If we removed last keyword, do not leave trailing
+ ;; white space at the end of line.
+ (let ((p (point)))
+ (save-excursion
+ (end-of-line)
+ (unless (= (skip-chars-backward " \t" p) 0)
+ (delete-region (point) (line-end-position)))))))
+ ((not what) (throw 'exit nil)) ; Nothing to do.
+ (t (insert-before-markers "\n")
+ (backward-char 1)
+ (when org-adapt-indentation
+ (indent-to-column (1+ (org-outline-level))))))
+ (when what
+ ;; Insert planning keyword.
+ (insert (cl-case what
+ (closed org-closed-string)
+ (deadline org-deadline-string)
+ (scheduled org-scheduled-string)
+ (otherwise (error "Invalid planning type: %s" what)))
+ " ")
+ ;; Insert associated timestamp.
+ (let ((ts (org-insert-time-stamp
+ time
+ (or org-time-was-given
+ (and (eq what 'closed) org-log-done-with-time))
+ (eq what 'closed)
+ nil nil (list org-end-time-was-given))))
+ (unless (eolp) (insert " "))
+ ts))))))
+
+(defvar org-log-note-marker (make-marker)
+ "Marker pointing at the entry where the note is to be inserted.")
(defvar org-log-note-purpose nil)
(defvar org-log-note-state nil)
(defvar org-log-note-previous-state nil)
-(defvar org-log-note-how nil)
(defvar org-log-note-extra nil)
(defvar org-log-note-window-configuration nil)
(defvar org-log-note-return-to (make-marker))
(defvar org-log-note-effective-time nil
"Remembered current time so that dynamically scoped
-`org-extend-today-until' affects tha timestamps in state change
-log")
+`org-extend-today-until' affects timestamps in state change log")
(defvar org-log-post-message nil
"Message to be displayed after a log note has been stored.
@@ -13304,85 +13651,92 @@ The auto-repeater uses this.")
"Add a note to the current entry.
This is done in the same way as adding a state change note."
(interactive)
- (org-add-log-setup 'note nil nil 'findpos nil))
+ (org-add-log-setup 'note))
-(defvar org-property-end-re)
-(defun org-add-log-setup (&optional purpose state prev-state
- findpos how extra)
+(defun org-log-beginning (&optional create)
+ "Return expected start of log notes in current entry.
+When optional argument CREATE is non-nil, the function creates
+a drawer to store notes, if necessary. Returned position ignores
+narrowing."
+ (org-with-wide-buffer
+ (let ((drawer (org-log-into-drawer)))
+ (cond
+ (drawer
+ (org-end-of-meta-data)
+ (let ((regexp (concat "^[ \t]*:" (regexp-quote drawer) ":[ \t]*$"))
+ (end (if (org-at-heading-p) (point)
+ (save-excursion (outline-next-heading) (point))))
+ (case-fold-search t))
+ (catch 'exit
+ ;; Try to find existing drawer.
+ (while (re-search-forward regexp end t)
+ (let ((element (org-element-at-point)))
+ (when (eq (org-element-type element) 'drawer)
+ (let ((cend (org-element-property :contents-end element)))
+ (when (and (not org-log-states-order-reversed) cend)
+ (goto-char cend)))
+ (throw 'exit nil))))
+ ;; No drawer found. Create one, if permitted.
+ (when create
+ (unless (bolp) (insert "\n"))
+ (let ((beg (point)))
+ (insert ":" drawer ":\n:END:\n")
+ (org-indent-region beg (point)))
+ (end-of-line -1)))))
+ (t
+ (org-end-of-meta-data org-log-state-notes-insert-after-drawers)
+ (skip-chars-forward " \t\n")
+ (beginning-of-line)
+ (unless org-log-states-order-reversed
+ (org-skip-over-state-notes)
+ (skip-chars-backward " \t\n")
+ (forward-line)))))
+ (if (bolp) (point) (line-beginning-position 2))))
+
+(defun org-add-log-setup (&optional purpose state prev-state how extra)
"Set up the post command hook to take a note.
If this is about to TODO state change, the new state is expected in STATE.
-When FINDPOS is non-nil, find the correct position for the note in
-the current entry. If not, assume that it can be inserted at point.
HOW is an indicator what kind of note should be created.
EXTRA is additional text that will be inserted into the notes buffer."
- (let* ((org-log-into-drawer (org-log-into-drawer))
- (drawer (cond ((stringp org-log-into-drawer)
- org-log-into-drawer)
- (org-log-into-drawer "LOGBOOK"))))
- (save-restriction
- (save-excursion
- (when findpos
- (org-back-to-heading t)
- (narrow-to-region (point) (save-excursion
- (outline-next-heading) (point)))
- (looking-at (concat org-outline-regexp "\\( *\\)[^\r\n]*"
- "\\(\n[^\r\n]*?" org-keyword-time-not-clock-regexp
- "[^\r\n]*\\)?"))
- (goto-char (match-end 0))
- (cond
- (drawer
- (if (re-search-forward (concat "^[ \t]*:" drawer ":[ \t]*$")
- nil t)
- (progn
- (goto-char (match-end 0))
- (or org-log-states-order-reversed
- (and (re-search-forward org-property-end-re nil t)
- (goto-char (1- (match-beginning 0))))))
- (insert "\n:" drawer ":\n:END:")
- (beginning-of-line 0)
- (org-indent-line)
- (beginning-of-line 2)
- (org-indent-line)
- (end-of-line 0)))
- ((and org-log-state-notes-insert-after-drawers
- (save-excursion
- (forward-line) (looking-at org-drawer-regexp)))
- (forward-line)
- (while (looking-at org-drawer-regexp)
- (goto-char (match-end 0))
- (re-search-forward org-property-end-re (point-max) t)
- (forward-line))
- (forward-line -1)))
- (unless org-log-states-order-reversed
- (and (= (char-after) ?\n) (forward-char 1))
- (org-skip-over-state-notes)
- (skip-chars-backward " \t\n\r")))
- (move-marker org-log-note-marker (point))
- (setq org-log-note-purpose purpose
- org-log-note-state state
- org-log-note-previous-state prev-state
- org-log-note-how how
- org-log-note-extra extra
- org-log-note-effective-time (org-current-effective-time))
- (add-hook 'post-command-hook 'org-add-log-note 'append)))))
+ (move-marker org-log-note-marker (point))
+ (setq org-log-note-purpose purpose
+ org-log-note-state state
+ org-log-note-previous-state prev-state
+ org-log-note-how how
+ org-log-note-extra extra
+ org-log-note-effective-time (org-current-effective-time))
+ (add-hook 'post-command-hook 'org-add-log-note 'append))
(defun org-skip-over-state-notes ()
"Skip past the list of State notes in an entry."
- (if (looking-at "\n[ \t]*- State") (forward-char 1))
(when (ignore-errors (goto-char (org-in-item-p)))
(let* ((struct (org-list-struct))
- (prevs (org-list-prevs-alist struct)))
- (while (looking-at "[ \t]*- State")
+ (prevs (org-list-prevs-alist struct))
+ (regexp
+ (concat "[ \t]*- +"
+ (replace-regexp-in-string
+ " +" " +"
+ (org-replace-escapes
+ (regexp-quote (cdr (assq 'state org-log-note-headings)))
+ `(("%d" . ,org-ts-regexp-inactive)
+ ("%D" . ,org-ts-regexp)
+ ("%s" . "\"\\S-+\"")
+ ("%S" . "\"\\S-+\"")
+ ("%t" . ,org-ts-regexp-inactive)
+ ("%T" . ,org-ts-regexp)
+ ("%u" . ".*?")
+ ("%U" . ".*?")))))))
+ (while (looking-at-p regexp)
(goto-char (or (org-list-get-next-item (point) struct prevs)
(org-list-get-item-end (point) struct)))))))
-(defun org-add-log-note (&optional purpose)
- "Pop up a window for taking a note, and add this note later at point."
+(defun org-add-log-note (&optional _purpose)
+ "Pop up a window for taking a note, and add this note later."
(remove-hook 'post-command-hook 'org-add-log-note)
(setq org-log-note-window-configuration (current-window-configuration))
(delete-other-windows)
(move-marker org-log-note-return-to (point))
- (org-pop-to-buffer-same-window (marker-buffer org-log-note-marker))
+ (pop-to-buffer-same-window (marker-buffer org-log-note-marker))
(goto-char org-log-note-marker)
(org-switch-to-buffer-other-window "*Org Note*")
(erase-buffer)
@@ -13411,23 +13765,23 @@ EXTRA is additional text that will be inserted into the notes buffer."
((eq org-log-note-purpose 'note)
"this entry")
(t (error "This should not happen")))))
- (if org-log-note-extra (insert org-log-note-extra))
- (org-set-local 'org-finish-function 'org-store-log-note)
+ (when org-log-note-extra (insert org-log-note-extra))
+ (setq-local org-finish-function 'org-store-log-note)
(run-hooks 'org-log-buffer-setup-hook)))
(defvar org-note-abort nil) ; dynamically scoped
(defun org-store-log-note ()
"Finish taking a log note, and insert it to where it belongs."
- (let ((txt (buffer-string)))
- (kill-buffer (current-buffer))
- (let ((note (cdr (assq org-log-note-purpose org-log-note-headings)))
- lines ind bul)
+ (let ((txt (prog1 (buffer-string)
+ (kill-buffer)))
+ (note (cdr (assq org-log-note-purpose org-log-note-headings)))
+ lines)
(while (string-match "\\`# .*\n[ \t\n]*" txt)
(setq txt (replace-match "" t t txt)))
- (if (string-match "\\s-+\\'" txt)
- (setq txt (replace-match "" t t txt)))
+ (when (string-match "\\s-+\\'" txt)
+ (setq txt (replace-match "" t t txt)))
(setq lines (org-split-string txt "\n"))
- (when (and note (string-match "\\S-" note))
+ (when (org-string-nw-p note)
(setq note
(org-replace-escapes
note
@@ -13445,74 +13799,83 @@ EXTRA is additional text that will be inserted into the notes buffer."
(cons "%D" (format-time-string
(org-time-stamp-format nil nil)
org-log-note-effective-time))
- (cons "%s" (if org-log-note-state
- (concat "\"" org-log-note-state "\"")
- ""))
- (cons "%S" (if org-log-note-previous-state
- (concat "\"" org-log-note-previous-state "\"")
- "\"\"")))))
- (if lines (setq note (concat note " \\\\")))
+ (cons "%s" (cond
+ ((not org-log-note-state) "")
+ ((string-match-p org-ts-regexp
+ org-log-note-state)
+ (format "\"[%s]\""
+ (substring org-log-note-state 1 -1)))
+ (t (format "\"%s\"" org-log-note-state))))
+ (cons "%S"
+ (cond
+ ((not org-log-note-previous-state) "")
+ ((string-match-p org-ts-regexp
+ org-log-note-previous-state)
+ (format "\"[%s]\""
+ (substring
+ org-log-note-previous-state 1 -1)))
+ (t (format "\"%s\""
+ org-log-note-previous-state)))))))
+ (when lines (setq note (concat note " \\\\")))
(push note lines))
- (when (or current-prefix-arg org-note-abort)
- (when org-log-into-drawer
- (org-remove-empty-drawer-at
- (if (stringp org-log-into-drawer) org-log-into-drawer "LOGBOOK")
- org-log-note-marker))
- (setq lines nil))
- (when lines
+ (when (and lines (not (or current-prefix-arg org-note-abort)))
(with-current-buffer (marker-buffer org-log-note-marker)
- (save-excursion
- (goto-char org-log-note-marker)
- (move-marker org-log-note-marker nil)
- (end-of-line 1)
- (if (not (bolp)) (let ((inhibit-read-only t)) (insert "\n")))
- (setq ind (save-excursion
- (if (ignore-errors (goto-char (org-in-item-p)))
- (let ((struct (org-list-struct)))
- (org-list-get-ind
- (org-list-get-top-point struct) struct))
- (skip-chars-backward " \r\t\n")
- (cond
- ((and (org-at-heading-p)
- org-adapt-indentation)
- (1+ (org-current-level)))
- ((org-at-heading-p) 0)
- (t (org-get-indentation))))))
- (setq bul (org-list-bullet-string "-"))
- (org-indent-line-to ind)
- (insert bul (pop lines))
- (let ((ind-body (+ (length bul) ind)))
- (while lines
- (insert "\n")
- (org-indent-line-to ind-body)
- (insert (pop lines))))
- (message "Note stored")
- (org-back-to-heading t)
- (org-cycle-hide-drawers 'children))
+ (org-with-wide-buffer
+ ;; Find location for the new note.
+ (goto-char org-log-note-marker)
+ (set-marker org-log-note-marker nil)
+ ;; Note associated to a clock is to be located right after
+ ;; the clock. Do not move point.
+ (unless (eq org-log-note-purpose 'clock-out)
+ (goto-char (org-log-beginning t)))
+ ;; Make sure point is at the beginning of an empty line.
+ (cond ((not (bolp)) (let ((inhibit-read-only t)) (insert "\n")))
+ ((looking-at "[ \t]*\\S-") (save-excursion (insert "\n"))))
+ ;; In an existing list, add a new item at the top level.
+ ;; Otherwise, indent line like a regular one.
+ (let ((itemp (org-in-item-p)))
+ (if itemp
+ (indent-line-to
+ (let ((struct (save-excursion
+ (goto-char itemp) (org-list-struct))))
+ (org-list-get-ind (org-list-get-top-point struct) struct)))
+ (org-indent-line)))
+ (insert (org-list-bullet-string "-") (pop lines))
+ (let ((ind (org-list-item-body-column (line-beginning-position))))
+ (dolist (line lines)
+ (insert "\n")
+ (indent-line-to ind)
+ (insert line)))
+ (message "Note stored")
+ (org-back-to-heading t)
+ (org-cycle-hide-drawers 'children))
;; Fix `buffer-undo-list' when `org-store-log-note' is called
;; from within `org-add-log-note' because `buffer-undo-list'
;; is then modified outside of `org-with-remote-undo'.
(when (eq this-command 'org-agenda-todo)
- (setcdr buffer-undo-list (cddr buffer-undo-list)))))))
- ;; Don't add undo information when called from `org-agenda-todo'
+ (setcdr buffer-undo-list (cddr buffer-undo-list))))))
+ ;; Don't add undo information when called from `org-agenda-todo'.
(let ((buffer-undo-list (eq this-command 'org-agenda-todo)))
(set-window-configuration org-log-note-window-configuration)
(with-current-buffer (marker-buffer org-log-note-return-to)
(goto-char org-log-note-return-to))
(move-marker org-log-note-return-to nil)
- (and org-log-post-message (message "%s" org-log-post-message))))
+ (when org-log-post-message (message "%s" org-log-post-message))))
-(defun org-remove-empty-drawer-at (drawer pos)
- "Remove an empty drawer DRAWER at position POS.
+(defun org-remove-empty-drawer-at (pos)
+ "Remove an empty drawer at position POS.
POS may also be a marker."
(with-current-buffer (if (markerp pos) (marker-buffer pos) (current-buffer))
- (save-excursion
- (save-restriction
- (widen)
- (goto-char pos)
- (if (org-in-regexp
- (concat "^[ \t]*:" drawer ":[ \t]*\n[ \t]*:END:[ \t]*\n?") 2)
- (replace-match ""))))))
+ (org-with-wide-buffer
+ (goto-char pos)
+ (let ((drawer (org-element-at-point)))
+ (when (and (memq (org-element-type drawer) '(drawer property-drawer))
+ (not (org-element-property :contents-begin drawer)))
+ (delete-region (org-element-property :begin drawer)
+ (progn (goto-char (org-element-property :end drawer))
+ (skip-chars-backward " \r\t\n")
+ (forward-line)
+ (point))))))))
(defvar org-ts-type nil)
(defun org-sparse-tree (&optional arg type)
@@ -13533,47 +13896,45 @@ D Show deadlines and scheduled items between a date range."
(interactive "P")
(setq type (or type org-sparse-tree-default-date-type))
(setq org-ts-type type)
- (message "Sparse tree: [/]regexp [t]odo [T]odo-kwd [m]atch [p]roperty
- [d]eadlines [b]efore-date [a]fter-date [D]ates range
- [c]ycle through date types: %s"
- (case type
+ (message "Sparse tree: [r]egexp [t]odo [T]odo-kwd [m]atch [p]roperty
+ \[d]eadlines [b]efore-date [a]fter-date [D]ates range
+ \[c]ycle through date types: %s"
+ (cl-case type
(all "all timestamps")
(scheduled "only scheduled")
(deadline "only deadline")
(active "only active timestamps")
(inactive "only inactive timestamps")
- (scheduled-or-deadline "scheduled/deadline")
(closed "with a closed time-stamp")
(otherwise "scheduled/deadline")))
(let ((answer (read-char-exclusive)))
- (case answer
+ (cl-case answer
(?c
(org-sparse-tree
arg
- (cadr (memq type '(scheduled-or-deadline all scheduled deadline active
- inactive closed)))))
- (?d (call-interactively #'org-check-deadlines))
- (?b (call-interactively #'org-check-before-date))
- (?a (call-interactively #'org-check-after-date))
- (?D (call-interactively #'org-check-dates-range))
- (?t (call-interactively #'org-show-todo-tree))
+ (cadr
+ (memq type '(nil all scheduled deadline active inactive closed)))))
+ (?d (call-interactively 'org-check-deadlines))
+ (?b (call-interactively 'org-check-before-date))
+ (?a (call-interactively 'org-check-after-date))
+ (?D (call-interactively 'org-check-dates-range))
+ (?t (call-interactively 'org-show-todo-tree))
(?T (org-show-todo-tree '(4)))
- (?m (call-interactively #'org-match-sparse-tree))
+ (?m (call-interactively 'org-match-sparse-tree))
((?p ?P)
- (let* ((kwd (org-icompleting-read
+ (let* ((kwd (completing-read
"Property: " (mapcar #'list (org-buffer-property-keys))))
- (value (org-icompleting-read
+ (value (completing-read
"Value: " (mapcar #'list (org-property-values kwd)))))
(unless (string-match "\\`{.*}\\'" value)
(setq value (concat "\"" value "\"")))
(org-match-sparse-tree arg (concat kwd "=" value))))
- ((?r ?R ?/) (call-interactively #'org-occur))
+ ((?r ?R ?/) (call-interactively 'org-occur))
(otherwise (user-error "No such sparse tree command \"%c\"" answer)))))
-(defvar org-occur-highlights nil
+(defvar-local org-occur-highlights nil
"List of overlays used for occur matches.")
-(make-variable-buffer-local 'org-occur-highlights)
-(defvar org-occur-parameters nil
+(defvar-local org-occur-parameters nil
"Parameters of the active org-occur calls.
This is a list, each call to org-occur pushes as cons cell,
containing the regular expression and the callback, onto the list.
@@ -13583,18 +13944,21 @@ will only contain one set of parameters. When the highlights are
removed (for example with `C-c C-c', or with the next edit (depending
on `org-remove-highlights-with-change'), this variable is emptied
as well.")
-(make-variable-buffer-local 'org-occur-parameters)
(defun org-occur (regexp &optional keep-previous callback)
"Make a compact tree which shows all matches of REGEXP.
-The tree will show the lines where the regexp matches, and all higher
-headlines above the match. It will also show the heading after the match,
-to make sure editing the matching entry is easy.
-If KEEP-PREVIOUS is non-nil, highlighting and exposing done by a previous
-call to `org-occur' will be kept, to allow stacking of calls to this
-command.
-If CALLBACK is non-nil, it is a function which is called to confirm
-that the match should indeed be shown."
+
+The tree will show the lines where the regexp matches, and any other context
+defined in `org-show-context-detail', which see.
+
+When optional argument KEEP-PREVIOUS is non-nil, highlighting and exposing
+done by a previous call to `org-occur' will be kept, to allow stacking of
+calls to this command.
+
+Optional argument CALLBACK can be a function of no argument. In this case,
+it is called with point at the end of the match, match data being set
+accordingly. Current match is shown only if the return value is non-nil.
+The function must neither move point nor alter narrowing."
(interactive "sRegexp: \nP")
(when (equal regexp "")
(user-error "Regexp cannot be empty"))
@@ -13604,32 +13968,35 @@ that the match should indeed be shown."
(let ((cnt 0))
(save-excursion
(goto-char (point-min))
- (if (or (not keep-previous) ; do not want to keep
- (not org-occur-highlights)) ; no previous matches
- ;; hide everything
- (org-overview))
- (while (re-search-forward regexp nil t)
- (when (or (not callback)
- (save-match-data (funcall callback)))
- (setq cnt (1+ cnt))
- (when org-highlight-sparse-tree-matches
- (org-highlight-new-match (match-beginning 0) (match-end 0)))
- (org-show-context 'occur-tree))))
+ (when (or (not keep-previous) ; do not want to keep
+ (not org-occur-highlights)) ; no previous matches
+ ;; hide everything
+ (org-overview))
+ (let ((case-fold-search (if (eq org-occur-case-fold-search 'smart)
+ (isearch-no-upper-case-p regexp t)
+ org-occur-case-fold-search)))
+ (while (re-search-forward regexp nil t)
+ (when (or (not callback)
+ (save-match-data (funcall callback)))
+ (setq cnt (1+ cnt))
+ (when org-highlight-sparse-tree-matches
+ (org-highlight-new-match (match-beginning 0) (match-end 0)))
+ (org-show-context 'occur-tree)))))
(when org-remove-highlights-with-change
- (org-add-hook 'before-change-functions 'org-remove-occur-highlights
- nil 'local))
+ (add-hook 'before-change-functions 'org-remove-occur-highlights
+ nil 'local))
(unless org-sparse-tree-open-archived-trees
(org-hide-archived-subtrees (point-min) (point-max)))
(run-hooks 'org-occur-hook)
- (if (org-called-interactively-p 'interactive)
- (message "%d match(es) for regexp %s" cnt regexp))
+ (when (called-interactively-p 'interactive)
+ (message "%d match(es) for regexp %s" cnt regexp))
cnt))
-(defun org-occur-next-match (&optional n reset)
+(defun org-occur-next-match (&optional n _reset)
"Function for `next-error-function' to find sparse tree matches.
N is the number of matches to move, when negative move backwards.
-RESET is entirely ignored - this function always goes back to the
-starting point when no match is found."
+This function always goes back to the starting point when no
+match is found."
(let* ((limit (if (< n 0) (point-min) (point-max)))
(search-func (if (< n 0)
'previous-single-char-property-change
@@ -13641,7 +14008,7 @@ starting point when no match is found."
(while (setq p1 (funcall search-func (point) 'org-type))
(when (equal p1 limit)
(goto-char pos)
- (error "No more matches"))
+ (user-error "No more matches"))
(when (equal (get-char-property p1 'org-type) 'org-occur)
(setq n (1- n))
(when (= n 0)
@@ -13649,65 +14016,75 @@ starting point when no match is found."
(throw 'exit (point))))
(goto-char p1))
(goto-char p1)
- (error "No more matches"))))
+ (user-error "No more matches"))))
(defun org-show-context (&optional key)
"Make sure point and context are visible.
-How much context is shown depends upon the variables
-`org-show-hierarchy-above', `org-show-following-heading',
-`org-show-entry-below' and `org-show-siblings'."
- (let ((heading-p (org-at-heading-p t))
- (hierarchy-p (org-get-alist-option org-show-hierarchy-above key))
- (following-p (org-get-alist-option org-show-following-heading key))
- (entry-p (org-get-alist-option org-show-entry-below key))
- (siblings-p (org-get-alist-option org-show-siblings key)))
- ;; Show heading or entry text
- (if (and heading-p (not entry-p))
- (org-flag-heading nil) ; only show the heading
- (and (or entry-p (outline-invisible-p) (org-invisible-p2))
- (org-show-hidden-entry))) ; show entire entry
- (when following-p
- ;; Show next sibling, or heading below text
- (save-excursion
- (and (if heading-p (org-goto-sibling) (outline-next-heading))
- (org-flag-heading nil))))
- (when siblings-p (org-show-siblings))
- (when hierarchy-p
- ;; show all higher headings, possibly with siblings
- (save-excursion
- (while (and (condition-case nil
- (progn (org-up-heading-all 1) t)
- (error nil))
- (not (bobp)))
- (org-flag-heading nil)
- (when siblings-p (org-show-siblings)))))))
+Optional argument KEY, when non-nil, is a symbol. See
+`org-show-context-detail' for allowed values and how much is to
+be shown."
+ (org-show-set-visibility
+ (cond ((symbolp org-show-context-detail) org-show-context-detail)
+ ((cdr (assq key org-show-context-detail)))
+ (t (cdr (assq 'default org-show-context-detail))))))
+
+(defun org-show-set-visibility (detail)
+ "Set visibility around point according to DETAIL.
+DETAIL is either nil, `minimal', `local', `ancestors', `lineage',
+`tree', `canonical' or t. See `org-show-context-detail' for more
+information."
+ ;; Show current heading and possibly its entry, following headline
+ ;; or all children.
+ (if (and (org-at-heading-p) (not (eq detail 'local)))
+ (org-flag-heading nil)
+ (org-show-entry)
+ ;; If point is hidden within a drawer or a block, make sure to
+ ;; expose it.
+ (dolist (o (overlays-at (point)))
+ (when (memq (overlay-get o 'invisible) '(org-hide-block outline))
+ (delete-overlay o)))
+ (unless (org-before-first-heading-p)
+ (org-with-limited-levels
+ (cl-case detail
+ ((tree canonical t) (org-show-children))
+ ((nil minimal ancestors))
+ (t (save-excursion
+ (outline-next-heading)
+ (org-flag-heading nil)))))))
+ ;; Show all siblings.
+ (when (eq detail 'lineage) (org-show-siblings))
+ ;; Show ancestors, possibly with their children.
+ (when (memq detail '(ancestors lineage tree canonical t))
+ (save-excursion
+ (while (org-up-heading-safe)
+ (org-flag-heading nil)
+ (when (memq detail '(canonical t)) (org-show-entry))
+ (when (memq detail '(tree canonical t)) (org-show-children))))))
(defvar org-reveal-start-hook nil
"Hook run before revealing a location.")
(defun org-reveal (&optional siblings)
"Show current entry, hierarchy above it, and the following headline.
-This can be used to show a consistent set of context around locations
-exposed with `org-show-hierarchy-above' or `org-show-following-heading'
-not t for the search context.
+
+This can be used to show a consistent set of context around
+locations exposed with `org-show-context'.
With optional argument SIBLINGS, on each level of the hierarchy all
siblings are shown. This repairs the tree structure to what it would
look like when opened with hierarchical calls to `org-cycle'.
-With double optional argument \\[universal-argument] \\[universal-argument], \
-go to the parent and show the
-entire tree."
+
+With a \\[universal-argument] \\[universal-argument] prefix, \
+go to the parent and show the entire tree."
(interactive "P")
(run-hooks 'org-reveal-start-hook)
- (let ((org-show-hierarchy-above t)
- (org-show-following-heading t)
- (org-show-siblings (if siblings t org-show-siblings)))
- (org-show-context nil))
- (when (equal siblings '(16))
- (save-excursion
- (when (org-up-heading-safe)
- (org-show-subtree)
- (run-hook-with-args 'org-cycle-hook 'subtree)))))
+ (cond ((equal siblings '(4)) (org-show-set-visibility 'canonical))
+ ((equal siblings '(16))
+ (save-excursion
+ (when (org-up-heading-safe)
+ (org-show-subtree)
+ (run-hook-with-args 'org-cycle-hook 'subtree))))
+ (t (org-show-set-visibility 'lineage))))
(defun org-highlight-new-match (beg end)
"Highlight from BEG to END and mark the highlight is an occur headline."
@@ -13716,13 +14093,13 @@ entire tree."
(overlay-put ov 'org-type 'org-occur)
(push ov org-occur-highlights)))
-(defun org-remove-occur-highlights (&optional beg end noremove)
+(defun org-remove-occur-highlights (&optional _beg _end noremove)
"Remove the occur highlights from the buffer.
BEG and END are ignored. If NOREMOVE is nil, remove this function
from the `before-change-functions' in the current buffer."
(interactive)
(unless org-inhibit-highlight-removal
- (mapc 'delete-overlay org-occur-highlights)
+ (mapc #'delete-overlay org-occur-highlights)
(setq org-occur-highlights nil)
(setq org-occur-parameters nil)
(unless noremove
@@ -13746,89 +14123,88 @@ from the `before-change-functions' in the current buffer."
(interactive)
(org-priority 'down))
-(defun org-priority (&optional action show)
+(defun org-priority (&optional action _show)
"Change the priority of an item.
ACTION can be `set', `up', `down', or a character."
(interactive "P")
(if (equal action '(4))
(org-show-priority)
- (unless org-enable-priority-commands
- (user-error "Priority commands are disabled"))
- (setq action (or action 'set))
- (let (current new news have remove)
- (save-excursion
- (org-back-to-heading t)
- (if (looking-at org-priority-regexp)
+ (unless org-enable-priority-commands
+ (user-error "Priority commands are disabled"))
+ (setq action (or action 'set))
+ (let (current new news have remove)
+ (save-excursion
+ (org-back-to-heading t)
+ (when (looking-at org-priority-regexp)
(setq current (string-to-char (match-string 2))
have t))
- (cond
- ((eq action 'remove)
- (setq remove t new ?\ ))
- ((or (eq action 'set)
- (if (featurep 'xemacs) (characterp action) (integerp action)))
- (if (not (eq action 'set))
- (setq new action)
- (message "Priority %c-%c, SPC to remove: "
- org-highest-priority org-lowest-priority)
- (save-match-data
- (setq new (read-char-exclusive))))
- (if (and (= (upcase org-highest-priority) org-highest-priority)
- (= (upcase org-lowest-priority) org-lowest-priority))
+ (cond
+ ((eq action 'remove)
+ (setq remove t new ?\ ))
+ ((or (eq action 'set)
+ (integerp action))
+ (if (not (eq action 'set))
+ (setq new action)
+ (message "Priority %c-%c, SPC to remove: "
+ org-highest-priority org-lowest-priority)
+ (save-match-data
+ (setq new (read-char-exclusive))))
+ (when (and (= (upcase org-highest-priority) org-highest-priority)
+ (= (upcase org-lowest-priority) org-lowest-priority))
(setq new (upcase new)))
- (cond ((equal new ?\ ) (setq remove t))
- ((or (< (upcase new) org-highest-priority) (> (upcase new) org-lowest-priority))
- (user-error "Priority must be between `%c' and `%c'"
- org-highest-priority org-lowest-priority))))
- ((eq action 'up)
- (setq new (if have
- (1- current) ; normal cycling
- ;; last priority was empty
- (if (eq last-command this-command)
- org-lowest-priority ; wrap around empty to lowest
- ;; default
- (if org-priority-start-cycle-with-default
- org-default-priority
- (1- org-default-priority))))))
- ((eq action 'down)
- (setq new (if have
- (1+ current) ; normal cycling
- ;; last priority was empty
- (if (eq last-command this-command)
- org-highest-priority ; wrap around empty to highest
- ;; default
- (if org-priority-start-cycle-with-default
- org-default-priority
- (1+ org-default-priority))))))
- (t (user-error "Invalid action")))
- (if (or (< (upcase new) org-highest-priority)
- (> (upcase new) org-lowest-priority))
+ (cond ((equal new ?\ ) (setq remove t))
+ ((or (< (upcase new) org-highest-priority) (> (upcase new) org-lowest-priority))
+ (user-error "Priority must be between `%c' and `%c'"
+ org-highest-priority org-lowest-priority))))
+ ((eq action 'up)
+ (setq new (if have
+ (1- current) ; normal cycling
+ ;; last priority was empty
+ (if (eq last-command this-command)
+ org-lowest-priority ; wrap around empty to lowest
+ ;; default
+ (if org-priority-start-cycle-with-default
+ org-default-priority
+ (1- org-default-priority))))))
+ ((eq action 'down)
+ (setq new (if have
+ (1+ current) ; normal cycling
+ ;; last priority was empty
+ (if (eq last-command this-command)
+ org-highest-priority ; wrap around empty to highest
+ ;; default
+ (if org-priority-start-cycle-with-default
+ org-default-priority
+ (1+ org-default-priority))))))
+ (t (user-error "Invalid action")))
+ (when (or (< (upcase new) org-highest-priority)
+ (> (upcase new) org-lowest-priority))
(if (and (memq action '(up down))
(not have) (not (eq last-command this-command)))
- ;; `new' is from default priority
+ ;; `new' is from default priority
(error
"The default can not be set, see `org-default-priority' why")
- ;; normal cycling: `new' is beyond highest/lowest priority
- ;; and is wrapped around to the empty priority
+ ;; normal cycling: `new' is beyond highest/lowest priority
+ ;; and is wrapped around to the empty priority
(setq remove t)))
- (setq news (format "%c" new))
- (if have
+ (setq news (format "%c" new))
+ (if have
+ (if remove
+ (replace-match "" t t nil 1)
+ (replace-match news t t nil 2))
(if remove
- (replace-match "" t t nil 1)
- (replace-match news t t nil 2))
- (if remove
- (user-error "No priority cookie found in line")
- (let ((case-fold-search nil))
- (looking-at org-todo-line-regexp))
- (if (match-end 2)
- (progn
- (goto-char (match-end 2))
- (insert " [#" news "]"))
- (goto-char (match-beginning 3))
- (insert "[#" news "] "))))
- (org-preserve-lc (org-set-tags nil 'align)))
- (if remove
- (message "Priority removed")
- (message "Priority of current item set to %s" news)))))
+ (user-error "No priority cookie found in line")
+ (let ((case-fold-search nil)) (looking-at org-todo-line-regexp))
+ (if (match-end 2)
+ (progn
+ (goto-char (match-end 2))
+ (insert " [#" news "]"))
+ (goto-char (match-beginning 3))
+ (insert "[#" news "] "))))
+ (org-set-tags nil 'align))
+ (if remove
+ (message "Priority removed")
+ (message "Priority of current item set to %s" news)))))
(defun org-show-priority ()
"Show the priority of the current item.
@@ -13863,6 +14239,7 @@ Can be set by the action argument to `org-scan-tags' and `org-map-entries'.")
(defvar org-scanner-tags nil
"The current tag list while the tags scanner is running.")
+
(defvar org-trust-scanner-tags nil
"Should `org-get-tags-at' use the tags for the scanner.
This is for internal dynamical scoping only.
@@ -13874,6 +14251,8 @@ obtain a list of properties. Building the tags list for each entry in such
a file becomes an N^2 operation - but with this variable set, it scales
as N.")
+(defvar org--matcher-tags-todo-only nil)
+
(defun org-scan-tags (action matcher todo-only &optional start-level)
"Scan headline tags with inheritance and produce output ACTION.
@@ -13882,11 +14261,14 @@ or `agenda' to produce an entry list for an agenda view. It can also be
a Lisp form or a function that should be called at each matched headline, in
this case the return value is a list of all return values from these calls.
-MATCHER is a Lisp form to be evaluated, testing if a given set of tags
-qualifies a headline for inclusion. When TODO-ONLY is non-nil,
-only lines with a not-done TODO keyword are included in the output.
-This should be the same variable that was scoped into
-and set by `org-make-tags-matcher' when it constructed MATCHER.
+MATCHER is a function accepting three arguments, returning
+a non-nil value whenever a given set of tags qualifies a headline
+for inclusion. See `org-make-tags-matcher' for more information.
+As a special case, it can also be set to t (respectively nil) in
+order to match all (respectively none) headline.
+
+When TODO-ONLY is non-nil, only lines with a not-done TODO
+keyword are included in the output.
START-LEVEL can be a string with asterisks, reducing the scope to
headlines matching this string."
@@ -13897,8 +14279,8 @@ headlines matching this string."
(concat "\\*\\{" (number-to-string start-level) "\\} ")
org-outline-regexp)
" *\\(\\<\\("
- (mapconcat 'regexp-quote org-todo-keywords-1 "\\|")
- (org-re "\\)\\>\\)? *\\(.*?\\)\\(:[[:alnum:]_@#%:]+:\\)?[ \t]*$")))
+ (mapconcat #'regexp-quote org-todo-keywords-1 "\\|")
+ "\\)\\>\\)? *\\(.*?\\)\\(:[[:alnum:]_@#%:]+:\\)?[ \t]*$"))
(props (list 'face 'default
'done-face 'org-agenda-done
'undone-face 'default
@@ -13915,8 +14297,9 @@ headlines matching this string."
lspos tags tags-list
(tags-alist (list (cons 0 org-file-tags)))
(llast 0) rtn rtn1 level category i txt
- todo marker entry priority)
- (when (not (or (member action '(agenda sparse-tree)) (functionp action)))
+ todo marker entry priority
+ ts-date ts-date-type ts-date-pair)
+ (unless (or (member action '(agenda sparse-tree)) (functionp action))
(setq action (list 'lambda nil action)))
(save-excursion
(goto-char (point-min))
@@ -13927,11 +14310,17 @@ headlines matching this string."
(re-search-forward re nil t))
(setq org-map-continue-from nil)
(catch :skip
- (setq todo (if (match-end 1) (org-match-string-no-properties 2))
- tags (if (match-end 4) (org-match-string-no-properties 4)))
+ (setq todo
+ ;; TODO: is the 1-2 difference a bug?
+ (when (match-end 1) (match-string-no-properties 2))
+ tags (when (match-end 4) (match-string-no-properties 4)))
(goto-char (setq lspos (match-beginning 0)))
(setq level (org-reduced-level (org-outline-level))
category (org-get-category))
+ (when (eq action 'agenda)
+ (setq ts-date-pair (org-agenda-entry-get-agenda-timestamp (point))
+ ts-date (car ts-date-pair)
+ ts-date-type (cdr ts-date-pair)))
(setq i llast llast level)
;; remove tag lists from same and sublevels
(while (>= i level)
@@ -13958,18 +14347,20 @@ headlines matching this string."
(when (and tags org-use-tag-inheritance
(or (not (eq t org-use-tag-inheritance))
org-tags-exclude-from-inheritance))
- ;; selective inheritance, remove uninherited ones
+ ;; Selective inheritance, remove uninherited ones.
(setcdr (car tags-alist)
(org-remove-uninherited-tags (cdar tags-alist))))
(when (and
;; eval matcher only when the todo condition is OK
(and (or (not todo-only) (member todo org-not-done-keywords))
- (let ((case-fold-search t) (org-trust-scanner-tags t))
- (eval matcher)))
+ (if (functionp matcher)
+ (let ((case-fold-search t) (org-trust-scanner-tags t))
+ (funcall matcher todo tags-list level))
+ matcher))
- ;; Call the skipper, but return t if it does not skip,
- ;; so that the `and' form continues evaluating
+ ;; Call the skipper, but return t if it does not
+ ;; skip, so that the `and' form continues evaluating.
(progn
(unless (eq action 'sparse-tree) (org-agenda-skip))
t)
@@ -13995,7 +14386,8 @@ headlines matching this string."
(if (eq org-tags-match-list-sublevels 'indented)
(make-string (1- level) ?.) "")
(org-get-heading))
- level category
+ (make-string level ?\s)
+ category
tags-list)
priority (org-get-priority txt))
(goto-char lspos)
@@ -14003,7 +14395,9 @@ headlines matching this string."
(org-add-props txt props
'org-marker marker 'org-hd-marker marker 'org-category category
'todo-state todo
- 'priority priority 'type "tagsmatch")
+ 'ts-date ts-date
+ 'priority priority
+ 'type (concat "tagsmatch" ts-date-type))
(push txt rtn))
((functionp action)
(setq org-map-continue-from nil)
@@ -14048,13 +14442,19 @@ headlines matching this string."
(defun org-match-sparse-tree (&optional todo-only match)
"Create a sparse tree according to tags string MATCH.
-MATCH can contain positive and negative selection of tags, like
-\"+WORK+URGENT-WITHBOSS\".
-If optional argument TODO-ONLY is non-nil, only select lines that are
-also TODO lines."
+
+MATCH is a string with match syntax. It can contain a selection
+of tags (\"+work+urgent-boss\"), properties (\"LEVEL>3\"), and
+TODO keywords (\"TODO=\\\"WAITING\\\"\") or a combination of
+those. See the manual for details.
+
+If optional argument TODO-ONLY is non-nil, only select lines that
+are also TODO tasks."
(interactive "P")
(org-agenda-prepare-buffers (list (current-buffer)))
- (org-scan-tags 'sparse-tree (cdr (org-make-tags-matcher match)) todo-only))
+ (let ((org--matcher-tags-todo-only todo-only))
+ (org-scan-tags 'sparse-tree (cdr (org-make-tags-matcher match))
+ org--matcher-tags-todo-only)))
(defalias 'org-tags-sparse-tree 'org-match-sparse-tree)
@@ -14062,15 +14462,17 @@ also TODO lines."
(defun org-cached-entry-get (pom property)
(if (or (eq t org-use-property-inheritance)
(and (stringp org-use-property-inheritance)
- (string-match org-use-property-inheritance property))
+ (let ((case-fold-search t))
+ (string-match-p org-use-property-inheritance property)))
(and (listp org-use-property-inheritance)
- (member property org-use-property-inheritance)))
- ;; Caching is not possible, check it directly
+ (member-ignore-case property org-use-property-inheritance)))
+ ;; Caching is not possible, check it directly.
(org-entry-get pom property 'inherit)
- ;; Get all properties, so that we can do complicated checks easily
- (cdr (assoc property (or org-cached-props
- (setq org-cached-props
- (org-entry-properties pom)))))))
+ ;; Get all properties, so we can do complicated checks easily.
+ (cdr (assoc-string property
+ (or org-cached-props
+ (setq org-cached-props (org-entry-properties pom)))
+ t))))
(defun org-global-tags-completion-table (&optional files)
"Return the list of all tags in all agenda buffer/files.
@@ -14079,186 +14481,173 @@ instead of the agenda files."
(save-excursion
(org-uniquify
(delq nil
- (apply 'append
+ (apply #'append
(mapcar
(lambda (file)
(set-buffer (find-file-noselect file))
- (append (org-get-buffer-tags)
- (mapcar (lambda (x) (if (stringp (car-safe x))
- (list (car-safe x)) nil))
- org-tag-alist)))
- (if (and files (car files))
- files
+ (mapcar (lambda (x)
+ (and (stringp (car-safe x))
+ (list (car-safe x))))
+ (or org-current-tag-alist (org-get-buffer-tags))))
+ (if (car-safe files) files
(org-agenda-files))))))))
(defun org-make-tags-matcher (match)
"Create the TAGS/TODO matcher form for the selection string MATCH.
-The variable `todo-only' is scoped dynamically into this function.
-It will be set to t if the matcher restricts matching to TODO entries,
-otherwise will not be touched.
-
-Returns a cons of the selection string MATCH and the constructed
-lisp form implementing the matcher. The matcher is to be evaluated
-at an Org entry, with point on the headline, and returns t if the
-entry matches the selection string MATCH. The returned lisp form
-references two variables with information about the entry, which
-must be bound around the form's evaluation: todo, the TODO keyword
-at the entry (or nil of none); and tags-list, the list of all tags
-at the entry including inherited ones. Additionally, the category
-of the entry (if any) must be specified as the text property
-'org-category on the headline.
-
-See also `org-scan-tags'.
-"
- (declare (special todo-only))
- (unless (boundp 'todo-only)
- (error "`org-make-tags-matcher' expects todo-only to be scoped in"))
+Returns a cons of the selection string MATCH and a function
+implementing the matcher.
+
+The matcher is to be called at an Org entry, with point on the
+headline, and returns non-nil if the entry matches the selection
+string MATCH. It must be called with three arguments: the TODO
+keyword at the entry (or nil if none), the list of all tags at
+the entry including inherited ones and the reduced level of the
+headline. Additionally, the category of the entry, if any, must
+be specified as the text property `org-category' on the headline.
+
+This function sets the variable `org--matcher-tags-todo-only' to
+a non-nil value if the matcher restricts matching to TODO
+entries, otherwise it is not touched.
+
+See also `org-scan-tags'."
(unless match
;; Get a new match request, with completion against the global
- ;; tags table and the local tags in current buffer
+ ;; tags table and the local tags in current buffer.
(let ((org-last-tags-completion-table
(org-uniquify
(delq nil (append (org-get-buffer-tags)
(org-global-tags-completion-table))))))
- (setq match (org-completing-read-no-i
- "Match: " 'org-tags-completion-function nil nil nil
- 'org-tags-history))))
+ (setq match
+ (completing-read
+ "Match: "
+ 'org-tags-completion-function nil nil nil 'org-tags-history))))
- ;; Parse the string and create a lisp form
(let ((match0 match)
- (re (org-re "^&?\\([-+:]\\)?\\({[^}]+}\\|LEVEL\\([<=>]\\{1,2\\}\\)\\([0-9]+\\)\\|\\(\\(?:[[:alnum:]_]+\\(?:\\\\-\\)*\\)+\\)\\([<>=]\\{1,2\\}\\)\\({[^}]+}\\|\"[^\"]*\"\\|-?[.0-9]+\\(?:[eE][-+]?[0-9]+\\)?\\)\\|[[:alnum:]_@#%]+\\)"))
- minus tag mm
- tagsmatch todomatch tagsmatcher todomatcher kwd matcher
- orterms orlist re-p str-p level-p level-op time-p
- prop-p pn pv po gv rest (start 0) (ss 0))
- ;; Expand group tags
+ (re "^&?\\([-+:]\\)?\\({[^}]+}\\|LEVEL\\([<=>]\\{1,2\\}\\)\\([0-9]+\\)\\|\\(\\(?:[[:alnum:]_]+\\(?:\\\\-\\)*\\)+\\)\\([<>=]\\{1,2\\}\\)\\({[^}]+}\\|\"[^\"]*\"\\|-?[.0-9]+\\(?:[eE][-+]?[0-9]+\\)?\\)\\|[[:alnum:]_@#%]+\\)")
+ (start 0)
+ tagsmatch todomatch tagsmatcher todomatcher)
+
+ ;; Expand group tags.
(setq match (org-tags-expand match))
;; Check if there is a TODO part of this match, which would be the
- ;; part after a "/". TO make sure that this slash is not part of
- ;; a property value to be matched against, we also check that there
- ;; is no " after that slash.
- ;; First, find the last slash
- (while (string-match "/+" match ss)
- (setq start (match-beginning 0) ss (match-end 0)))
+ ;; part after a "/". To make sure that this slash is not part of
+ ;; a property value to be matched against, we also check that
+ ;; there is no / after that slash. First, find the last slash.
+ (let ((s 0))
+ (while (string-match "/+" match s)
+ (setq start (match-beginning 0))
+ (setq s (match-end 0))))
(if (and (string-match "/+" match start)
- (not (save-match-data (string-match "\"" match start))))
- ;; match contains also a todo-matching request
+ (not (string-match-p "\"" match start)))
+ ;; Match contains also a TODO-matching request.
(progn
- (setq tagsmatch (substring match 0 (match-beginning 0))
- todomatch (substring match (match-end 0)))
- (if (string-match "^!" todomatch)
- (setq todo-only t todomatch (substring todomatch 1)))
- (if (string-match "^\\s-*$" todomatch)
- (setq todomatch nil)))
- ;; only matching tags
- (setq tagsmatch match todomatch nil))
-
- ;; Make the tags matcher
- (if (or (not tagsmatch) (not (string-match "\\S-" tagsmatch)))
- (setq tagsmatcher t)
- (setq orterms (org-split-string tagsmatch "|") orlist nil)
- (dolist (term orterms)
- (while (and (equal (substring term -1) "\\") orterms)
- (setq term (concat term "|" (pop orterms)))) ; repair bad split
- (while (string-match re term)
- (setq rest (substring term (match-end 0))
- minus (and (match-end 1)
- (equal (match-string 1 term) "-"))
- tag (save-match-data (replace-regexp-in-string
- "\\\\-" "-"
- (match-string 2 term)))
- re-p (equal (string-to-char tag) ?{)
- level-p (match-end 4)
- prop-p (match-end 5)
- mm (cond
- (re-p `(org-match-any-p ,(substring tag 1 -1) tags-list))
- (level-p
- (setq level-op (org-op-to-function (match-string 3 term)))
- `(,level-op level ,(string-to-number
- (match-string 4 term))))
- (prop-p
- (setq pn (match-string 5 term)
- po (match-string 6 term)
- pv (match-string 7 term)
- re-p (equal (string-to-char pv) ?{)
- str-p (equal (string-to-char pv) ?\")
- time-p (save-match-data
- (string-match "^\"[[<].*[]>]\"$" pv))
- pv (if (or re-p str-p) (substring pv 1 -1) pv))
- (if time-p (setq pv (org-matcher-time pv)))
- (setq po (org-op-to-function po (if time-p 'time str-p)))
- (cond
- ((equal pn "CATEGORY")
- (setq gv '(get-text-property (point) 'org-category)))
- ((equal pn "TODO")
- (setq gv 'todo))
- (t
- (setq gv `(org-cached-entry-get nil ,pn))))
- (if re-p
- (if (eq po 'org<>)
- `(not (string-match ,pv (or ,gv "")))
- `(string-match ,pv (or ,gv "")))
- (if str-p
- `(,po (or ,gv "") ,pv)
- `(,po (string-to-number (or ,gv ""))
- ,(string-to-number pv) ))))
- (t `(member ,tag tags-list)))
- mm (if minus (list 'not mm) mm)
- term rest)
- (push mm tagsmatcher))
- (push (if (> (length tagsmatcher) 1)
- (cons 'and tagsmatcher)
- (car tagsmatcher))
- orlist)
- (setq tagsmatcher nil))
- (setq tagsmatcher (if (> (length orlist) 1) (cons 'or orlist) (car orlist)))
- (setq tagsmatcher
- (list 'progn '(setq org-cached-props nil) tagsmatcher)))
- ;; Make the todo matcher
- (if (or (not todomatch) (not (string-match "\\S-" todomatch)))
- (setq todomatcher t)
- (setq orterms (org-split-string todomatch "|") orlist nil)
- (dolist (term orterms)
- (while (string-match re term)
- (setq minus (and (match-end 1)
- (equal (match-string 1 term) "-"))
- kwd (match-string 2 term)
- re-p (equal (string-to-char kwd) ?{)
- term (substring term (match-end 0))
- mm (if re-p
- `(string-match ,(substring kwd 1 -1) todo)
- (list 'equal 'todo kwd))
- mm (if minus (list 'not mm) mm))
- (push mm todomatcher))
- (push (if (> (length todomatcher) 1)
- (cons 'and todomatcher)
- (car todomatcher))
- orlist)
- (setq todomatcher nil))
- (setq todomatcher (if (> (length orlist) 1)
- (cons 'or orlist) (car orlist))))
-
- ;; Return the string and lisp forms of the matcher
- (setq matcher (if todomatcher
- (list 'and tagsmatcher todomatcher)
- tagsmatcher))
- (when todo-only
- (setq matcher (list 'and '(member todo org-not-done-keywords)
- matcher)))
- (cons match0 matcher)))
-
-(defun org-tags-expand (match &optional single-as-list downcased)
+ (setq tagsmatch (substring match 0 (match-beginning 0)))
+ (setq todomatch (substring match (match-end 0)))
+ (when (string-prefix-p "!" todomatch)
+ (setq org--matcher-tags-todo-only t)
+ (setq todomatch (substring todomatch 1)))
+ (when (string-match "\\`\\s-*\\'" todomatch)
+ (setq todomatch nil)))
+ ;; Only matching tags.
+ (setq tagsmatch match)
+ (setq todomatch nil))
+
+ ;; Make the tags matcher.
+ (when (org-string-nw-p tagsmatch)
+ (let ((orlist nil)
+ (orterms (org-split-string tagsmatch "|"))
+ term)
+ (while (setq term (pop orterms))
+ (while (and (equal (substring term -1) "\\") orterms)
+ (setq term (concat term "|" (pop orterms)))) ;repair bad split.
+ (while (string-match re term)
+ (let* ((rest (substring term (match-end 0)))
+ (minus (and (match-end 1)
+ (equal (match-string 1 term) "-")))
+ (tag (save-match-data
+ (replace-regexp-in-string
+ "\\\\-" "-" (match-string 2 term))))
+ (regexp (eq (string-to-char tag) ?{))
+ (levelp (match-end 4))
+ (propp (match-end 5))
+ (mm
+ (cond
+ (regexp `(org-match-any-p ,(substring tag 1 -1) tags-list))
+ (levelp
+ `(,(org-op-to-function (match-string 3 term))
+ level
+ ,(string-to-number (match-string 4 term))))
+ (propp
+ (let* ((gv (pcase (upcase (match-string 5 term))
+ ("CATEGORY"
+ '(get-text-property (point) 'org-category))
+ ("TODO" 'todo)
+ (p `(org-cached-entry-get nil ,p))))
+ (pv (match-string 7 term))
+ (regexp (eq (string-to-char pv) ?{))
+ (strp (eq (string-to-char pv) ?\"))
+ (timep (string-match-p "^\"[[<].*[]>]\"$" pv))
+ (po (org-op-to-function (match-string 6 term)
+ (if timep 'time strp))))
+ (setq pv (if (or regexp strp) (substring pv 1 -1) pv))
+ (when timep (setq pv (org-matcher-time pv)))
+ (cond ((and regexp (eq po 'org<>))
+ `(not (string-match ,pv (or ,gv ""))))
+ (regexp `(string-match ,pv (or ,gv "")))
+ (strp `(,po (or ,gv "") ,pv))
+ (t
+ `(,po
+ (string-to-number (or ,gv ""))
+ ,(string-to-number pv))))))
+ (t `(member ,tag tags-list)))))
+ (push (if minus `(not ,mm) mm) tagsmatcher)
+ (setq term rest)))
+ (push `(and ,@tagsmatcher) orlist)
+ (setq tagsmatcher nil))
+ (setq tagsmatcher `(progn (setq org-cached-props nil) (or ,@orlist)))))
+
+ ;; Make the TODO matcher.
+ (when (org-string-nw-p todomatch)
+ (let ((orlist nil))
+ (dolist (term (org-split-string todomatch "|"))
+ (while (string-match re term)
+ (let* ((minus (and (match-end 1)
+ (equal (match-string 1 term) "-")))
+ (kwd (match-string 2 term))
+ (regexp (eq (string-to-char kwd) ?{))
+ (mm (if regexp `(string-match ,(substring kwd 1 -1) todo)
+ `(equal todo ,kwd))))
+ (push (if minus `(not ,mm) mm) todomatcher))
+ (setq term (substring term (match-end 0))))
+ (push (if (> (length todomatcher) 1)
+ (cons 'and todomatcher)
+ (car todomatcher))
+ orlist)
+ (setq todomatcher nil))
+ (setq todomatcher (cons 'or orlist))))
+
+ ;; Return the string and function of the matcher. If no
+ ;; tags-specific or todo-specific matcher exists, match
+ ;; everything.
+ (let ((matcher (if (and tagsmatcher todomatcher)
+ `(and ,tagsmatcher ,todomatcher)
+ (or tagsmatcher todomatcher t))))
+ (when org--matcher-tags-todo-only
+ (setq matcher `(and (member todo org-not-done-keywords) ,matcher)))
+ (cons match0 `(lambda (todo tags-list level) ,matcher)))))
+
+(defun org-tags-expand (match &optional single-as-list downcased tags-already-expanded)
"Expand group tags in MATCH.
This replaces every group tag in MATCH with a regexp tag search.
For example, a group tag \"Work\" defined as { Work : Lab Conf }
will be replaced like this:
- Work => {\\(?:Work\\|Lab\\|Conf\\)}
- +Work => +{\\(?:Work\\|Lab\\|Conf\\)}
- -Work => -{\\(?:Work\\|Lab\\|Conf\\)}
+ Work => {\\<\\(?:Work\\|Lab\\|Conf\\)\\>}
+ +Work => +{\\<\\(?:Work\\|Lab\\|Conf\\)\\>}
+ -Work => -{\\<\\(?:Work\\|Lab\\|Conf\\)\\>}
Replacing by a regexp preserves the structure of the match.
E.g., this expansion
@@ -14268,6 +14657,12 @@ E.g., this expansion
will match anything tagged with \"Lab\" and \"Home\", or tagged
with \"Conf\" and \"Home\" or tagged with \"Work\" and \"home\".
+A group tag in MATCH can contain regular expressions of its own.
+For example, a group tag \"Proj\" defined as { Proj : {P@.+} }
+will be replaced like this:
+
+ Proj => {\\<\\(?:Proj\\)\\>\\|P@.+}
+
When the optional argument SINGLE-AS-LIST is non-nil, MATCH is
assumed to be a single group tag, and the function will return
the list of tags in this group.
@@ -14276,34 +14671,113 @@ When DOWNCASE is non-nil, expand downcased TAGS."
(if org-group-tags
(let* ((case-fold-search t)
(stable org-mode-syntax-table)
- (tal (or org-tag-groups-alist-for-agenda
- org-tag-groups-alist))
- (tal (if downcased
- (mapcar (lambda(tg) (mapcar 'downcase tg)) tal) tal))
- (tml (mapcar 'car tal))
- (rtnmatch match) rpl)
- ;; @ and _ are allowed as word-components in tags
+ (taggroups (or org-tag-groups-alist-for-agenda org-tag-groups-alist))
+ (taggroups (if downcased
+ (mapcar (lambda (tg) (mapcar #'downcase tg))
+ taggroups)
+ taggroups))
+ (taggroups-keys (mapcar #'car taggroups))
+ (return-match (if downcased (downcase match) match))
+ (count 0)
+ (work-already-expanded tags-already-expanded)
+ regexps-in-match tags-in-group regexp-in-group regexp-in-group-escaped)
+ ;; @ and _ are allowed as word-components in tags.
(modify-syntax-entry ?@ "w" stable)
(modify-syntax-entry ?_ "w" stable)
- (while (and tml
+ ;; Temporarily replace regexp-expressions in the match-expression.
+ (while (string-match "{.+?}" return-match)
+ (cl-incf count)
+ (push (match-string 0 return-match) regexps-in-match)
+ (setq return-match (replace-match (format "<%d>" count) t nil return-match)))
+ (while (and taggroups-keys
(with-syntax-table stable
(string-match
(concat "\\(?1:[+-]?\\)\\(?2:\\<"
- (regexp-opt tml) "\\>\\)")
- rtnmatch)))
- (let* ((dir (match-string 1 rtnmatch))
- (tag (match-string 2 rtnmatch))
+ (regexp-opt taggroups-keys) "\\>\\)")
+ return-match)))
+ (let* ((dir (match-string 1 return-match))
+ (tag (match-string 2 return-match))
(tag (if downcased (downcase tag) tag)))
- (setq tml (delete tag tml))
- (when (not (get-text-property 0 'grouptag (match-string 2 rtnmatch)))
- (setq rpl (append (org-uniquify rpl) (assoc tag tal)))
- (setq rpl (concat dir "{\\<" (regexp-opt rpl) "\\>}"))
- (if (stringp rpl) (org-add-props rpl '(grouptag t)))
- (setq rtnmatch (replace-match rpl t t rtnmatch)))))
+ (unless (or (get-text-property 0 'grouptag (match-string 2 return-match))
+ (member tag work-already-expanded))
+ (setq tags-in-group (assoc tag taggroups))
+ (push tag work-already-expanded)
+ ;; Recursively expand each tag in the group, if the tag hasn't
+ ;; already been expanded. Restore the match-data after all recursive calls.
+ (save-match-data
+ (let (tags-expanded)
+ (dolist (x (cdr tags-in-group))
+ (if (and (member x taggroups-keys)
+ (not (member x work-already-expanded)))
+ (setq tags-expanded
+ (delete-dups
+ (append
+ (org-tags-expand x t downcased
+ work-already-expanded)
+ tags-expanded)))
+ (setq tags-expanded
+ (append (list x) tags-expanded)))
+ (setq work-already-expanded
+ (delete-dups
+ (append tags-expanded
+ work-already-expanded))))
+ (setq tags-in-group
+ (delete-dups (cons (car tags-in-group)
+ tags-expanded)))))
+ ;; Filter tag-regexps from tags.
+ (setq regexp-in-group-escaped
+ (delq nil (mapcar (lambda (x)
+ (if (stringp x)
+ (and (equal "{" (substring x 0 1))
+ (equal "}" (substring x -1))
+ x)
+ x))
+ tags-in-group))
+ regexp-in-group
+ (mapcar (lambda (x)
+ (substring x 1 -1))
+ regexp-in-group-escaped)
+ tags-in-group
+ (delq nil (mapcar (lambda (x)
+ (if (stringp x)
+ (and (not (equal "{" (substring x 0 1)))
+ (not (equal "}" (substring x -1)))
+ x)
+ x))
+ tags-in-group)))
+ ;; If single-as-list, do no more in the while-loop.
+ (if (not single-as-list)
+ (progn
+ (when regexp-in-group
+ (setq regexp-in-group
+ (concat "\\|"
+ (mapconcat 'identity regexp-in-group
+ "\\|"))))
+ (setq tags-in-group
+ (concat dir
+ "{\\<"
+ (regexp-opt tags-in-group)
+ "\\>"
+ regexp-in-group
+ "}"))
+ (when (stringp tags-in-group)
+ (org-add-props tags-in-group '(grouptag t)))
+ (setq return-match
+ (replace-match tags-in-group t t return-match)))
+ (setq tags-in-group
+ (append regexp-in-group-escaped tags-in-group))))
+ (setq taggroups-keys (delete tag taggroups-keys))))
+ ;; Add the regular expressions back into the match-expression again.
+ (while regexps-in-match
+ (setq return-match (replace-regexp-in-string (format "<%d>" count)
+ (pop regexps-in-match)
+ return-match t t))
+ (cl-decf count))
(if single-as-list
- (or (reverse rpl) (list rtnmatch))
- rtnmatch))
- (if single-as-list (list (if downcased (downcase match) match))
+ (if tags-in-group tags-in-group (list return-match))
+ return-match))
+ (if single-as-list
+ (list (if downcased (downcase match) match))
match)))
(defun org-op-to-function (op &optional stringp)
@@ -14371,7 +14845,7 @@ epoch to the beginning of today (00:00)."
(defvar org-add-colon-after-tag-completion nil) ;; dynamically scoped param
(defvar org-tags-overlay (make-overlay 1 1))
-(org-detach-overlay org-tags-overlay)
+(delete-overlay org-tags-overlay)
(defun org-get-local-tags-at (&optional pos)
"Get a list of tags defined in the current headline."
@@ -14405,10 +14879,9 @@ ignore inherited ones."
(org-back-to-heading t)
(while (not (equal lastpos (point)))
(setq lastpos (point))
- (when (looking-at
- (org-re "[^\r\n]+?:\\([[:alnum:]_@#%:]+\\):[ \t]*$"))
+ (when (looking-at ".+?:\\([[:alnum:]_@#%:]+\\):[ \t]*$")
(setq ltags (org-split-string
- (org-match-string-no-properties 1) ":"))
+ (match-string-no-properties 1) ":"))
(when parent
(setq ltags (mapcar 'org-add-prop-inherited ltags)))
(setq tags (append
@@ -14417,7 +14890,7 @@ ignore inherited ones."
ltags)
tags)))
(or org-use-tag-inheritance (throw 'done t))
- (if local (throw 'done t))
+ (when local (throw 'done t))
(or (org-up-heading-safe) (error nil))
(setq parent t)))
(error nil)))))
@@ -14439,7 +14912,7 @@ If ONOFF is `on' or `off', don't toggle but set to this state."
(let (res current)
(save-excursion
(org-back-to-heading t)
- (if (re-search-forward (org-re "[ \t]:\\([[:alnum:]_@#%:]+\\):[ \t]*$")
+ (if (re-search-forward "[ \t]:\\([[:alnum:]_@#%:]+\\):[ \t]*$"
(point-at-eol) t)
(progn
(setq current (match-string 1))
@@ -14465,29 +14938,24 @@ If ONOFF is `on' or `off', don't toggle but set to this state."
(run-hooks 'org-after-tags-change-hook))
res))
-(defun org-align-tags-here (to-col)
- ;; Assumes that this is a headline
- "Align tags on the current headline to TO-COL."
- (let ((pos (point)) (col (current-column)) ncol tags-l p)
- (beginning-of-line 1)
- (if (and (looking-at (org-re ".*?\\([ \t]+\\)\\(:[[:alnum:]_@#%:]+:\\)[ \t]*$"))
- (< pos (match-beginning 2)))
- (progn
- (setq tags-l (- (match-end 2) (match-beginning 2)))
- (goto-char (match-beginning 1))
- (insert " ")
- (delete-region (point) (1+ (match-beginning 2)))
- (setq ncol (max (current-column)
- (1+ col)
- (if (> to-col 0)
- to-col
- (- (abs to-col) tags-l))))
- (setq p (point))
- (insert (make-string (- ncol (current-column)) ?\ ))
- (setq ncol (current-column))
- (when indent-tabs-mode (tabify p (point-at-eol)))
- (org-move-to-column (min ncol col)))
- (goto-char pos))))
+(defun org--align-tags-here (to-col)
+ "Align tags on the current headline to TO-COL.
+Assume point is on a headline."
+ (let ((pos (point)))
+ (beginning-of-line)
+ (if (or (not (looking-at ".*?\\([ \t]+\\)\\(:[[:alnum:]_@#%:]+:\\)[ \t]*$"))
+ (>= pos (match-beginning 2)))
+ ;; No tags or point within tags: do not align.
+ (goto-char pos)
+ (goto-char (match-beginning 1))
+ (let ((shift (max (- (if (>= to-col 0) to-col
+ (- (abs to-col) (string-width (match-string 2))))
+ (current-column))
+ 1)))
+ (replace-match (make-string shift ?\s) nil nil nil 1)
+ ;; Preserve initial position, if possible. In any case, stop
+ ;; before tags.
+ (when (< pos (point)) (goto-char pos))))))
(defun org-set-tags-command (&optional arg just-align)
"Call the set-tags command for the current entry."
@@ -14517,7 +14985,8 @@ If DATA is nil or the empty string, any tags will be removed."
(when data
(save-excursion
(org-back-to-heading t)
- (when (looking-at org-complex-heading-regexp)
+ (when (let ((case-fold-search nil))
+ (looking-at org-complex-heading-regexp))
(if (match-end 5)
(progn
(goto-char (match-beginning 5))
@@ -14528,11 +14997,11 @@ If DATA is nil or the empty string, any tags will be removed."
(insert " " data)
(org-set-tags nil 'align)))
(beginning-of-line 1)
- (if (looking-at ".*?\\([ \t]+\\)$")
- (delete-region (match-beginning 1) (match-end 1))))))
+ (when (looking-at ".*?\\([ \t]+\\)$")
+ (delete-region (match-beginning 1) (match-end 1))))))
(defun org-align-all-tags ()
- "Align the tags i all headings."
+ "Align the tags in all headings."
(interactive)
(save-excursion
(or (ignore-errors (org-back-to-heading t))
@@ -14549,106 +15018,124 @@ When JUST-ALIGN is non-nil, only align tags."
(interactive "P")
(if (and (org-region-active-p) org-loop-over-headlines-in-active-region)
(let ((cl (if (eq org-loop-over-headlines-in-active-region 'start-level)
- 'region-start-level 'region))
- org-loop-over-headlines-in-active-region)
- (org-map-entries
- ;; We don't use ARG and JUST-ALIGN here because these args
- ;; are not useful when looping over headlines.
- `(org-set-tags)
- org-loop-over-headlines-in-active-region
- cl (if (outline-invisible-p) (org-end-of-subtree nil t))))
- (let* ((re org-outline-regexp-bol)
- (current (unless arg (org-get-tags-string)))
- (col (current-column))
- (org-setting-tags t)
- table current-tags inherited-tags ; computed below when needed
- tags p0 c0 c1 rpl di tc level)
+ 'region-start-level
+ 'region))
+ org-loop-over-headlines-in-active-region)
+ (org-map-entries
+ ;; We don't use ARG and JUST-ALIGN here because these args
+ ;; are not useful when looping over headlines.
+ #'org-set-tags
+ org-loop-over-headlines-in-active-region
+ cl
+ '(when (org-invisible-p) (org-end-of-subtree nil t))))
+ (let ((org-setting-tags t))
(if arg
- (save-excursion
- (goto-char (point-min))
- (let ((buffer-invisibility-spec (org-inhibit-invisibility)))
- (while (re-search-forward re nil t)
- (org-set-tags nil t)
- (end-of-line 1)))
- (message "All tags realigned to column %d" org-tags-column))
- (if just-align
- (setq tags current)
- ;; Get a new set of tags from the user
- (save-excursion
- (setq table (append org-tag-persistent-alist
- (or org-tag-alist (org-get-buffer-tags))
- (and
- org-complete-tags-always-offer-all-agenda-tags
- (org-global-tags-completion-table
- (org-agenda-files))))
- org-last-tags-completion-table table
- current-tags (org-split-string current ":")
- inherited-tags (nreverse
- (nthcdr (length current-tags)
- (nreverse (org-get-tags-at))))
- tags
- (if (or (eq t org-use-fast-tag-selection)
- (and org-use-fast-tag-selection
- (delq nil (mapcar 'cdr table))))
- (org-fast-tag-selection
- current-tags inherited-tags table
- (if org-fast-tag-selection-include-todo
- org-todo-key-alist))
- (let ((org-add-colon-after-tag-completion (< 1 (length table))))
- (org-trim
- (org-icompleting-read "Tags: "
- 'org-tags-completion-function
- nil nil current 'org-tags-history))))))
- (while (string-match "[-+&]+" tags)
- ;; No boolean logic, just a list
- (setq tags (replace-match ":" t t tags))))
-
- (setq tags (replace-regexp-in-string "[,]" ":" tags))
-
- (if org-tags-sort-function
- (setq tags (mapconcat 'identity
- (sort (org-split-string
- tags (org-re "[^[:alnum:]_@#%]+"))
- org-tags-sort-function) ":")))
-
- (if (string-match "\\`[\t ]*\\'" tags)
- (setq tags "")
- (unless (string-match ":$" tags) (setq tags (concat tags ":")))
- (unless (string-match "^:" tags) (setq tags (concat ":" tags))))
-
- ;; Insert new tags at the correct column
- (beginning-of-line 1)
- (setq level (or (and (looking-at org-outline-regexp)
- (- (match-end 0) (point) 1))
- 1))
- (cond
- ((and (equal current "") (equal tags "")))
- ((re-search-forward
- (concat "\\([ \t]*" (regexp-quote current) "\\)[ \t]*$")
- (point-at-eol) t)
- (if (equal tags "")
- (setq rpl "")
- (goto-char (match-beginning 0))
- (setq c0 (current-column)
- ;; compute offset for the case of org-indent-mode active
- di (if (org-bound-and-true-p org-indent-mode)
- (* (1- org-indent-indentation-per-level) (1- level))
- 0)
- p0 (if (equal (char-before) ?*) (1+ (point)) (point))
- tc (+ org-tags-column (if (> org-tags-column 0) (- di) di))
- c1 (max (1+ c0) (if (> tc 0) tc (- (- tc) (length tags))))
- rpl (concat (make-string (max 0 (- c1 c0)) ?\ ) tags)))
- (replace-match rpl t t)
- (and (not (featurep 'xemacs)) c0 indent-tabs-mode (tabify p0 (point)))
- tags)
- (t (error "Tags alignment failed")))
- (org-move-to-column col)
- (unless just-align
- (run-hooks 'org-after-tags-change-hook))))))
+ (save-excursion
+ (goto-char (point-min))
+ (while (re-search-forward org-outline-regexp-bol nil t)
+ (org-set-tags nil t)
+ (end-of-line))
+ (message "All tags realigned to column %d" org-tags-column))
+ (let* ((current (org-get-tags-string))
+ (tags
+ (if just-align current
+ ;; Get a new set of tags from the user.
+ (save-excursion
+ (let* ((seen)
+ (table
+ (setq
+ org-last-tags-completion-table
+ ;; Uniquify tags in alists, yet preserve
+ ;; structure (i.e., keywords).
+ (delq nil
+ (mapcar
+ (lambda (pair)
+ (let ((head (car pair)))
+ (cond ((symbolp head) pair)
+ ((member head seen) nil)
+ (t (push head seen)
+ pair))))
+ (append
+ (or org-current-tag-alist
+ (org-get-buffer-tags))
+ (and
+ org-complete-tags-always-offer-all-agenda-tags
+ (org-global-tags-completion-table
+ (org-agenda-files))))))))
+ (current-tags (org-split-string current ":"))
+ (inherited-tags
+ (nreverse (nthcdr (length current-tags)
+ (nreverse (org-get-tags-at))))))
+ (replace-regexp-in-string
+ "\\([-+&]+\\|,\\)"
+ ":"
+ (if (or (eq t org-use-fast-tag-selection)
+ (and org-use-fast-tag-selection
+ (delq nil (mapcar #'cdr table))))
+ (org-fast-tag-selection
+ current-tags inherited-tags table
+ (and org-fast-tag-selection-include-todo
+ org-todo-key-alist))
+ (let ((org-add-colon-after-tag-completion
+ (< 1 (length table))))
+ (org-trim
+ (completing-read
+ "Tags: "
+ #'org-tags-completion-function
+ nil nil current 'org-tags-history))))))))))
+
+ (when org-tags-sort-function
+ (setq tags
+ (mapconcat
+ #'identity
+ (sort (org-split-string tags "[^[:alnum:]_@#%]+")
+ org-tags-sort-function)
+ ":")))
+
+ (if (or (string= ":" tags)
+ (string= "::" tags))
+ (setq tags ""))
+ (if (not (org-string-nw-p tags)) (setq tags "")
+ (unless (string-suffix-p ":" tags) (setq tags (concat tags ":")))
+ (unless (string-prefix-p ":" tags) (setq tags (concat ":" tags))))
+
+ ;; Insert new tags at the correct column.
+ (unless (equal current tags)
+ (save-excursion
+ (beginning-of-line)
+ (let ((case-fold-search nil))
+ (looking-at org-complex-heading-regexp))
+ ;; Remove current tags, if any.
+ (when (match-end 5) (replace-match "" nil nil nil 5))
+ ;; Insert new tags, if any. Otherwise, remove trailing
+ ;; white spaces.
+ (end-of-line)
+ (if (not (equal tags ""))
+ ;; When text is being inserted on an invisible
+ ;; region boundary, it can be inadvertently sucked
+ ;; into invisibility.
+ (outline-flag-region (point) (progn (insert " " tags) (point)) nil)
+ (skip-chars-backward " \t")
+ (delete-region (point) (line-end-position)))))
+ ;; Align tags, if any. Fix tags column if `org-indent-mode'
+ ;; is on.
+ (unless (equal tags "")
+ (let* ((level (save-excursion
+ (beginning-of-line)
+ (skip-chars-forward "\\*")))
+ (offset (if (bound-and-true-p org-indent-mode)
+ (* (1- org-indent-indentation-per-level)
+ (1- level))
+ 0))
+ (tags-column
+ (+ org-tags-column
+ (if (> org-tags-column 0) (- offset) offset))))
+ (org--align-tags-here tags-column))))
+ (unless just-align (run-hooks 'org-after-tags-change-hook))))))
(defun org-change-tag-in-region (beg end tag off)
"Add or remove TAG for each entry in the region.
-This works in the agenda, and also in an org-mode buffer."
+This works in the agenda, and also in an Org buffer."
(interactive
(list (region-beginning) (region-end)
(let ((org-last-tags-completion-table
@@ -14657,37 +15144,37 @@ This works in the agenda, and also in an org-mode buffer."
(delq nil (append (org-get-buffer-tags)
(org-global-tags-completion-table))))
(org-global-tags-completion-table))))
- (org-icompleting-read
+ (completing-read
"Tag: " 'org-tags-completion-function nil nil nil
'org-tags-history))
(progn
(message "[s]et or [r]emove? ")
(equal (read-char-exclusive) ?r))))
- (if (fboundp 'deactivate-mark) (deactivate-mark))
+ (when (fboundp 'deactivate-mark) (deactivate-mark))
(let ((agendap (equal major-mode 'org-agenda-mode))
l1 l2 m buf pos newhead (cnt 0))
(goto-char end)
(setq l2 (1- (org-current-line)))
(goto-char beg)
(setq l1 (org-current-line))
- (loop for l from l1 to l2 do
- (org-goto-line l)
- (setq m (get-text-property (point) 'org-hd-marker))
- (when (or (and (derived-mode-p 'org-mode) (org-at-heading-p))
- (and agendap m))
- (setq buf (if agendap (marker-buffer m) (current-buffer))
- pos (if agendap m (point)))
- (with-current-buffer buf
- (save-excursion
- (save-restriction
- (goto-char pos)
- (setq cnt (1+ cnt))
- (org-toggle-tag tag (if off 'off 'on))
- (setq newhead (org-get-heading)))))
- (and agendap (org-agenda-change-all-lines newhead m))))
+ (cl-loop for l from l1 to l2 do
+ (org-goto-line l)
+ (setq m (get-text-property (point) 'org-hd-marker))
+ (when (or (and (derived-mode-p 'org-mode) (org-at-heading-p))
+ (and agendap m))
+ (setq buf (if agendap (marker-buffer m) (current-buffer))
+ pos (if agendap m (point)))
+ (with-current-buffer buf
+ (save-excursion
+ (save-restriction
+ (goto-char pos)
+ (setq cnt (1+ cnt))
+ (org-toggle-tag tag (if off 'off 'on))
+ (setq newhead (org-get-heading)))))
+ (and agendap (org-agenda-change-all-lines newhead m))))
(message "Tag :%s: %s in %d headings" tag (if off "removed" "set") cnt)))
-(defun org-tags-completion-function (string predicate &optional flag)
+(defun org-tags-completion-function (string _predicate &optional flag)
(let (s1 s2 rtn (ctable org-last-tags-completion-table)
(confirm (lambda (x) (stringp (car x)))))
(if (string-match "^\\(.*[-+:&,|]\\)\\([^-+:&,|]*\\)$" string)
@@ -14698,12 +15185,12 @@ This works in the agenda, and also in an org-mode buffer."
((eq flag nil)
;; try completion
(setq rtn (try-completion s2 ctable confirm))
- (if (stringp rtn)
- (setq rtn
- (concat s1 s2 (substring rtn (length s2))
- (if (and org-add-colon-after-tag-completion
- (assoc rtn ctable))
- ":" ""))))
+ (when (stringp rtn)
+ (setq rtn
+ (concat s1 s2 (substring rtn (length s2))
+ (if (and org-add-colon-after-tag-completion
+ (assoc rtn ctable))
+ ":" ""))))
rtn)
((eq flag t)
;; all-completions
@@ -14722,8 +15209,8 @@ Also insert END."
(defun org-fast-tag-show-exit (flag)
(save-excursion
(org-goto-line 3)
- (if (re-search-forward "[ \t]+Next change exits" (point-at-eol) t)
- (replace-match ""))
+ (when (re-search-forward "[ \t]+Next change exits" (point-at-eol) t)
+ (replace-match ""))
(when flag
(end-of-line 1)
(org-move-to-column (- (window-width) 19) t)
@@ -14732,11 +15219,8 @@ Also insert END."
(defun org-set-current-tags-overlay (current prefix)
"Add an overlay to CURRENT tag with PREFIX."
(let ((s (concat ":" (mapconcat 'identity current ":") ":")))
- (if (featurep 'xemacs)
- (org-overlay-display org-tags-overlay (concat prefix s)
- 'secondary-selection)
- (put-text-property 0 (length s) 'face '(secondary-selection org-tag) s)
- (org-overlay-display org-tags-overlay (concat prefix s)))))
+ (put-text-property 0 (length s) 'face '(secondary-selection org-tag) s)
+ (org-overlay-display org-tags-overlay (concat prefix s))))
(defvar org-last-tag-selection-key nil)
(defun org-fast-tag-selection (current inherited table &optional todo-table)
@@ -14759,15 +15243,14 @@ Returns the new tags string, or nil to not change the current settings."
(ncol (/ (- (window-width) 4) fwidth))
(i-face 'org-done)
(c-face 'org-todo)
- tg cnt c char c1 c2 ntable tbl rtn
+ tg cnt e c char c1 c2 ntable tbl rtn
ov-start ov-end ov-prefix
(exit-after-next org-fast-tag-selection-single-key)
(done-keywords org-done-keywords)
- groups ingroup)
+ groups ingroup intaggroup)
(save-excursion
(beginning-of-line 1)
- (if (looking-at
- (org-re ".*[ \t]\\(:[[:alnum:]_@#%:]+:\\)[ \t]*$"))
+ (if (looking-at ".*[ \t]\\(:[[:alnum:]_@#%:]+:\\)[ \t]*$")
(setq ov-start (match-beginning 1)
ov-end (match-end 1)
ov-prefix "")
@@ -14788,32 +15271,41 @@ Returns the new tags string, or nil to not change the current settings."
(set-window-buffer (split-window-vertically) (get-buffer-create " *Org tags*"))
(org-switch-to-buffer-other-window " *Org tags*"))
(erase-buffer)
- (org-set-local 'org-done-keywords done-keywords)
+ (setq-local org-done-keywords done-keywords)
(org-fast-tag-insert "Inherited" inherited i-face "\n")
(org-fast-tag-insert "Current" current c-face "\n\n")
(org-fast-tag-show-exit exit-after-next)
(org-set-current-tags-overlay current ov-prefix)
(setq tbl fulltable char ?a cnt 0)
- (dolist (e tbl)
+ (while (setq e (pop tbl))
(cond
- ((equal (car e) :startgroup)
+ ((eq (car e) :startgroup)
(push '() groups) (setq ingroup t)
- (when (not (= cnt 0))
+ (unless (zerop cnt)
(setq cnt 0)
(insert "\n"))
(insert (if (cdr e) (format "%s: " (cdr e)) "") "{ "))
- ((equal (car e) :endgroup)
+ ((eq (car e) :endgroup)
(setq ingroup nil cnt 0)
(insert "}" (if (cdr e) (format " (%s) " (cdr e)) "") "\n"))
+ ((eq (car e) :startgrouptag)
+ (setq intaggroup t)
+ (unless (zerop cnt)
+ (setq cnt 0)
+ (insert "\n"))
+ (insert "[ "))
+ ((eq (car e) :endgrouptag)
+ (setq intaggroup nil cnt 0)
+ (insert "]\n"))
((equal e '(:newline))
- (when (not (= cnt 0))
+ (unless (zerop cnt)
(setq cnt 0)
(insert "\n")
(setq e (car tbl))
(while (equal (car tbl) '(:newline))
(insert "\n")
(setq tbl (cdr tbl)))))
- ((equal e '(:grouptags)) nil)
+ ((equal e '(:grouptags)) (insert " : "))
(t
(setq tg (copy-sequence (car e)) c2 nil)
(if (cdr e)
@@ -14827,27 +15319,27 @@ Returns the new tags string, or nil to not change the current settings."
(setq char (1+ char)))
(setq c2 c1))
(setq c (or c2 char)))
- (if ingroup (push tg (car groups)))
+ (when ingroup (push tg (car groups)))
(setq tg (org-add-props tg nil 'face
(cond
((not (assoc tg table))
(org-get-todo-face tg))
((member tg current) c-face)
((member tg inherited) i-face))))
- (if (equal (caar tbl) :grouptags)
- (org-add-props tg nil 'face 'org-tag-group))
- (if (and (= cnt 0) (not ingroup)) (insert " "))
+ (when (equal (caar tbl) :grouptags)
+ (org-add-props tg nil 'face 'org-tag-group))
+ (when (and (zerop cnt) (not ingroup) (not intaggroup)) (insert " "))
(insert "[" c "] " tg (make-string
(- fwidth 4 (length tg)) ?\ ))
(push (cons tg c) ntable)
- (when (= (setq cnt (1+ cnt)) ncol)
+ (when (= (cl-incf cnt) ncol)
(insert "\n")
- (if ingroup (insert " "))
+ (when (or ingroup intaggroup) (insert " "))
(setq cnt 0)))))
(setq ntable (nreverse ntable))
(insert "\n")
(goto-char (point-min))
- (if (not expert) (org-fit-window-to-buffer))
+ (unless expert (org-fit-window-to-buffer))
(setq rtn
(catch 'exit
(while t
@@ -14873,53 +15365,51 @@ Returns the new tags string, or nil to not change the current settings."
(org-fit-window-to-buffer)))
((or (= c ?\C-g)
(and (= c ?q) (not (rassoc c ntable))))
- (org-detach-overlay org-tags-overlay)
+ (delete-overlay org-tags-overlay)
(setq quit-flag t))
((= c ?\ )
(setq current nil)
- (if exit-after-next (setq exit-after-next 'now)))
+ (when exit-after-next (setq exit-after-next 'now)))
((= c ?\t)
(condition-case nil
- (setq tg (org-icompleting-read
+ (setq tg (completing-read
"Tag: "
(or buffer-tags
(with-current-buffer buf
- (org-get-buffer-tags)))))
+ (setq buffer-tags
+ (org-get-buffer-tags))))))
(quit (setq tg "")))
(when (string-match "\\S-" tg)
- (add-to-list 'buffer-tags (list tg))
+ (cl-pushnew (list tg) buffer-tags :test #'equal)
(if (member tg current)
(setq current (delete tg current))
(push tg current)))
- (if exit-after-next (setq exit-after-next 'now)))
+ (when exit-after-next (setq exit-after-next 'now)))
((setq e (rassoc c todo-table) tg (car e))
(with-current-buffer buf
(save-excursion (org-todo tg)))
- (if exit-after-next (setq exit-after-next 'now)))
+ (when exit-after-next (setq exit-after-next 'now)))
((setq e (rassoc c ntable) tg (car e))
(if (member tg current)
(setq current (delete tg current))
- (loop for g in groups do
- (if (member tg g)
- (mapc (lambda (x)
- (setq current (delete x current)))
- g)))
+ (cl-loop for g in groups do
+ (when (member tg g)
+ (dolist (x g) (setq current (delete x current)))))
(push tg current))
- (if exit-after-next (setq exit-after-next 'now))))
+ (when exit-after-next (setq exit-after-next 'now))))
;; Create a sorted list
(setq current
(sort current
(lambda (a b)
(assoc b (cdr (memq (assoc a ntable) ntable))))))
- (if (eq exit-after-next 'now) (throw 'exit t))
+ (when (eq exit-after-next 'now) (throw 'exit t))
(goto-char (point-min))
(beginning-of-line 2)
(delete-region (point) (point-at-eol))
(org-fast-tag-insert "Current" current c-face)
(org-set-current-tags-overlay current ov-prefix)
- (while (re-search-forward
- (org-re "\\[.\\] \\([[:alnum:]_@#%]+\\)") nil t)
+ (while (re-search-forward "\\[.\\] \\([[:alnum:]_@#%]+\\)" nil t)
(setq tg (match-string 1))
(add-text-properties
(match-beginning 1) (match-end 1)
@@ -14929,7 +15419,7 @@ Returns the new tags string, or nil to not change the current settings."
((member tg inherited) i-face)
(t (get-text-property (match-beginning 1) 'face))))))
(goto-char (point-min)))))
- (org-detach-overlay org-tags-overlay)
+ (delete-overlay org-tags-overlay)
(if rtn
(mapconcat 'identity current ":")
nil))))
@@ -14940,8 +15430,8 @@ Returns the new tags string, or nil to not change the current settings."
(user-error "Not on a heading"))
(save-excursion
(beginning-of-line 1)
- (if (looking-at (org-re ".*[ \t]\\(:[[:alnum:]_@#%:]+:\\)[ \t]*$"))
- (org-match-string-no-properties 1)
+ (if (looking-at ".*[ \t]\\(:[[:alnum:]_@#%:]+:\\)[ \t]*$")
+ (match-string-no-properties 1)
"")))
(defun org-get-tags ()
@@ -14950,19 +15440,20 @@ Returns the new tags string, or nil to not change the current settings."
(defun org-get-buffer-tags ()
"Get a table of all tags used in the buffer, for completion."
- (let (tags)
- (save-excursion
- (goto-char (point-min))
- (while (re-search-forward
- (org-re "[ \t]:\\([[:alnum:]_@#%:]+\\):[ \t\r\n]") nil t)
- (when (equal (char-after (point-at-bol 0)) ?*)
- (mapc (lambda (x) (add-to-list 'tags x))
- (org-split-string (org-match-string-no-properties 1) ":")))))
- (mapc (lambda (s) (add-to-list 'tags s)) org-file-tags)
- (mapcar 'list tags)))
+ (org-with-wide-buffer
+ (goto-char (point-min))
+ (let ((tag-re (concat org-outline-regexp-bol
+ "\\(?:.*?[ \t]\\)?:\\([[:alnum:]_@#%:]+\\):[ \t]*$"))
+ tags)
+ (while (re-search-forward tag-re nil t)
+ (dolist (tag (org-split-string (match-string-no-properties 1) ":"))
+ (push tag tags)))
+ (mapcar #'list (append org-file-tags (org-uniquify tags))))))
;;;; The mapping API
+(defvar org-agenda-skip-comment-trees)
+(defvar org-agenda-skip-function)
(defun org-map-entries (func &optional match scope &rest skip)
"Call FUNC at each headline selected by MATCH in SCOPE.
@@ -15032,13 +15523,12 @@ a *different* entry, you cannot use these techniques."
(car (org-delete-all '(comment archive) skip)))
(org-tags-match-list-sublevels t)
(start-level (eq scope 'region-start-level))
- matcher file res
+ matcher res
org-todo-keywords-for-agenda
org-done-keywords-for-agenda
org-todo-keyword-alist-for-agenda
- org-drawers-for-agenda
org-tag-alist-for-agenda
- todo-only)
+ org--matcher-tags-todo-only)
(cond
((eq match t) (setq matcher t))
@@ -15071,7 +15561,9 @@ a *different* entry, you cannot use these techniques."
(progn
(org-agenda-prepare-buffers
(and buffer-file-name (list buffer-file-name)))
- (setq res (org-scan-tags func matcher todo-only start-level)))
+ (setq res
+ (org-scan-tags
+ func matcher org--matcher-tags-todo-only start-level)))
;; Get the right scope
(cond
((and scope (listp scope) (symbolp (car scope)))
@@ -15088,22 +15580,21 @@ a *different* entry, you cannot use these techniques."
(org-agenda-prepare-buffers scope)
(dolist (file scope)
(with-current-buffer (org-find-base-buffer-visiting file)
- (save-excursion
- (save-restriction
- (widen)
- (goto-char (point-min))
- (setq res (append res (org-scan-tags func matcher todo-only))))))))))
+ (org-with-wide-buffer
+ (goto-char (point-min))
+ (setq res
+ (append
+ res
+ (org-scan-tags
+ func matcher org--matcher-tags-todo-only)))))))))
res)))
-;;;; Properties
-
-;;; Setting and retrieving properties
+;;; Properties API
(defconst org-special-properties
- '("TODO" "TAGS" "ALLTAGS" "DEADLINE" "SCHEDULED" "CLOCK" "CLOSED" "PRIORITY"
- "TIMESTAMP" "TIMESTAMP_IA" "BLOCKED" "FILE" "CLOCKSUM" "CLOCKSUM_T")
- "The special properties valid in Org-mode.
-
+ '("ALLTAGS" "BLOCKED" "CLOCKSUM" "CLOCKSUM_T" "CLOSED" "DEADLINE" "FILE"
+ "ITEM" "PRIORITY" "SCHEDULED" "TAGS" "TIMESTAMP" "TIMESTAMP_IA" "TODO")
+ "The special properties valid in Org mode.
These are properties that are not defined in the property drawer,
but in some other way.")
@@ -15112,59 +15603,86 @@ but in some other way.")
"LOCATION" "LOGGING" "COLUMNS" "VISIBILITY"
"TABLE_EXPORT_FORMAT" "TABLE_EXPORT_FILE"
"EXPORT_OPTIONS" "EXPORT_TEXT" "EXPORT_FILE_NAME"
- "EXPORT_TITLE" "EXPORT_AUTHOR" "EXPORT_DATE"
+ "EXPORT_TITLE" "EXPORT_AUTHOR" "EXPORT_DATE" "UNNUMBERED"
"ORDERED" "NOBLOCKING" "COOKIE_DATA" "LOG_INTO_DRAWER" "REPEAT_TO_STATE"
"CLOCK_MODELINE_TOTAL" "STYLE" "HTML_CONTAINER_CLASS")
- "Some properties that are used by Org-mode for various purposes.
+ "Some properties that are used by Org mode for various purposes.
Being in this list makes sure that they are offered for completion.")
-(defconst org-property-start-re "^[ \t]*:PROPERTIES:[ \t]*$"
- "Regular expression matching the first line of a property drawer.")
-
-(defconst org-property-end-re "^[ \t]*:END:[ \t]*$"
- "Regular expression matching the last line of a property drawer.")
-
-(defconst org-clock-drawer-start-re "^[ \t]*:CLOCK:[ \t]*$"
- "Regular expression matching the first line of a property drawer.")
-
-(defconst org-clock-drawer-end-re "^[ \t]*:END:[ \t]*$"
- "Regular expression matching the first line of a property drawer.")
-
-(defconst org-property-drawer-re
- (concat "\\(" org-property-start-re "\\)[^\000]*\\("
- org-property-end-re "\\)\n?")
- "Matches an entire property drawer.")
+(defun org--valid-property-p (property)
+ "Non nil when string PROPERTY is a valid property name."
+ (not
+ (or (equal property "")
+ (string-match-p "\\s-" property))))
+
+(defun org--update-property-plist (key val props)
+ "Associate KEY to VAL in alist PROPS.
+Modifications are made by side-effect. Return new alist."
+ (let* ((appending (string= (substring key -1) "+"))
+ (key (if appending (substring key 0 -1) key))
+ (old (assoc-string key props t)))
+ (if (not old) (cons (cons key val) props)
+ (setcdr old (if appending (concat (cdr old) " " val) val))
+ props)))
+
+(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."
+ (org-with-wide-buffer
+ (when beg (goto-char beg))
+ (unless (org-before-first-heading-p)
+ (let ((beg (cond (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))))))))
-(defconst org-clock-drawer-re
- (concat "\\(" org-clock-drawer-start-re "\\)[^\000]*\\("
- org-property-end-re "\\)\n?")
- "Matches an entire clock drawer.")
+(defun org-at-property-p ()
+ "Non-nil when point is inside a property drawer.
+See `org-property-re' for match data, if applicable."
+ (save-excursion
+ (beginning-of-line)
+ (and (looking-at org-property-re)
+ (let ((property-drawer (save-match-data (org-get-property-block))))
+ (and property-drawer
+ (>= (point) (car property-drawer))
+ (< (point) (cdr property-drawer)))))))
(defun org-property-action ()
"Do an action on properties."
(interactive)
- (let (c)
- (org-at-property-p)
- (message "Property Action: [s]et [d]elete [D]elete globally [c]ompute")
- (setq c (read-char-exclusive))
- (cond
- ((equal c ?s)
- (call-interactively 'org-set-property))
- ((equal c ?d)
- (call-interactively 'org-delete-property))
- ((equal c ?D)
- (call-interactively 'org-delete-property-globally))
- ((equal c ?c)
- (call-interactively 'org-compute-property-at-point))
- (t (user-error "No such property action %c" c)))))
+ (unless (org-at-property-p) (user-error "Not at a property"))
+ (message "Property Action: [s]et [d]elete [D]elete globally [c]ompute")
+ (let ((c (read-char-exclusive)))
+ (cl-case c
+ (?s (call-interactively #'org-set-property))
+ (?d (call-interactively #'org-delete-property))
+ (?D (call-interactively #'org-delete-property-globally))
+ (?c (call-interactively #'org-compute-property-at-point))
+ (otherwise (user-error "No such property action %c" c)))))
(defun org-inc-effort ()
"Increment the value of the effort property in the current entry."
(interactive)
(org-set-effort nil t))
-(defvar org-clock-effort) ;; Defined in org-clock.el
-(defvar org-clock-current-task) ;; Defined in org-clock.el
+(defvar org-clock-effort) ; Defined in org-clock.el.
+(defvar org-clock-current-task) ; Defined in org-clock.el.
(defun org-set-effort (&optional value increment)
"Set the effort property of the current entry.
With numerical prefix arg, use the nth allowed value, 0 stands for the
@@ -15172,7 +15690,7 @@ With numerical prefix arg, use the nth allowed value, 0 stands for the
When INCREMENT is non-nil, set the property to the next allowed value."
(interactive "P")
- (if (equal value 0) (setq value 10))
+ (when (equal value 0) (setq value 10))
(let* ((completion-ignore-case t)
(prop org-effort-property)
(cur (org-entry-get nil prop))
@@ -15186,7 +15704,7 @@ When INCREMENT is non-nil, set the property to the next allowed value."
(or (car (nth (1- value) allowed))
(car (org-last allowed))))
((and allowed increment)
- (or (caadr (member (list cur) allowed))
+ (or (cl-caadr (member (list cur) allowed))
(user-error "Allowed effort values are not set")))
(allowed
(message "Select 1-9,0, [RET%s]: %s"
@@ -15196,231 +15714,295 @@ When INCREMENT is non-nil, set the property to the next allowed value."
(if (equal rpl ?\r)
cur
(setq rpl (- rpl ?0))
- (if (equal rpl 0) (setq rpl 10))
+ (when (equal rpl 0) (setq rpl 10))
(if (and (> rpl 0) (<= rpl (length allowed)))
(car (nth (1- rpl) allowed))
(org-completing-read "Effort: " allowed nil))))
(t
- (let (org-completion-use-ido org-completion-use-iswitchb)
- (org-completing-read
- (concat "Effort " (if (and cur (string-match "\\S-" cur))
- (concat "[" cur "]") "")
- ": ")
- existing nil nil "" nil cur))))))
+ (org-completing-read
+ (concat "Effort" (and cur (string-match "\\S-" cur)
+ (concat " [" cur "]"))
+ ": ")
+ existing nil nil "" nil cur)))))
(unless (equal (org-entry-get nil prop) val)
(org-entry-put nil prop val))
- (save-excursion
- (org-back-to-heading t)
- (put-text-property (point-at-bol) (point-at-eol) 'org-effort val))
- (when (string= heading org-clock-current-task)
- (setq org-clock-effort (get-text-property (point-at-bol) 'org-effort))
+ (org-refresh-property
+ '((effort . identity)
+ (effort-minutes . org-duration-string-to-minutes))
+ val)
+ (when (equal heading (bound-and-true-p org-clock-current-task))
+ (setq org-clock-effort (get-text-property (point-at-bol) 'effort))
(org-clock-update-mode-line))
(message "%s is now %s" prop val)))
-(defun org-at-property-p ()
- "Is cursor inside a property drawer?"
- (save-excursion
- (when (equal 'node-property (car (org-element-at-point)))
- (beginning-of-line 1)
- (looking-at org-property-re))))
+(defun org-entry-properties (&optional pom which)
+ "Get all properties of the current entry.
+
+When POM is a buffer position, get all properties from the entry
+there instead.
+
+This includes the TODO keyword, the tags, time strings for
+deadline, scheduled, and clocking, and any additional properties
+defined in the entry.
-(defun org-get-property-block (&optional beg end force)
- "Return the (beg . end) range of the body of the property drawer.
-BEG and END are the beginning and end of the current subtree, or of
-the part before the first headline. If they are not given, they will
-be found. If the drawer does not exist and FORCE is non-nil, create
-the drawer."
- (catch 'exit
- (save-excursion
- (let* ((beg (or beg (and (org-before-first-heading-p) (point-min))
- (progn (org-back-to-heading t) (point))))
- (end (or end (and (not (outline-next-heading)) (point-max))
- (point))))
- (goto-char beg)
- (if (re-search-forward org-property-start-re end t)
- (setq beg (1+ (match-end 0)))
- (if force
- (save-excursion
- (org-insert-property-drawer)
- (setq end (progn (outline-next-heading) (point))))
- (throw 'exit nil))
- (goto-char beg)
- (if (re-search-forward org-property-start-re end t)
- (setq beg (1+ (match-end 0)))))
- (if (re-search-forward org-property-end-re end t)
- (setq end (match-beginning 0))
- (or force (throw 'exit nil))
- (goto-char beg)
- (setq end beg)
- (org-indent-line)
- (insert ":END:\n"))
- (cons beg end)))))
-
-(defun org-entry-properties (&optional pom which specific)
- "Get all properties of the entry at point-or-marker POM.
-This includes the TODO keyword, the tags, time strings for deadline,
-scheduled, and clocking, and any additional properties defined in the
-entry. The return value is an alist, keys may occur multiple times
-if the property key was used several times.
-POM may also be nil, in which case the current entry is used.
If WHICH is nil or `all', get all properties. If WHICH is
-`special' or `standard', only get that subclass. If WHICH
-is a string only get exactly this property. SPECIFIC can be a string, the
-specific property we are interested in. Specifying it can speed
-things up because then unnecessary parsing is avoided."
- (setq which (or which 'all))
- (org-with-wide-buffer
- (org-with-point-at pom
- (let ((clockstr (substring org-clock-string 0 -1))
- (excluded '("TODO" "TAGS" "ALLTAGS" "PRIORITY" "BLOCKED"))
- (case-fold-search nil)
- beg end range props sum-props key key1 value string clocksum clocksumt)
- (when (and (derived-mode-p 'org-mode)
- (ignore-errors (org-back-to-heading t)))
- (setq beg (point))
- (setq sum-props (get-text-property (point) 'org-summaries))
- (setq clocksum (get-text-property (point) :org-clock-minutes)
- clocksumt (get-text-property (point) :org-clock-minutes-today))
- (outline-next-heading)
- (setq end (point))
- (when (memq which '(all special))
- ;; Get the special properties, like TODO and tags
- (goto-char beg)
- (when (and (or (not specific) (string= specific "TODO"))
- (looking-at org-todo-line-regexp) (match-end 2))
- (push (cons "TODO" (org-match-string-no-properties 2)) props))
- (when (and (or (not specific) (string= specific "PRIORITY"))
- (looking-at org-priority-regexp))
- (push (cons "PRIORITY" (org-match-string-no-properties 2)) props))
- (when (or (not specific) (string= specific "FILE"))
- (push (cons "FILE" buffer-file-name) props))
- (when (and (or (not specific) (string= specific "TAGS"))
- (setq value (org-get-tags-string))
- (string-match "\\S-" value))
- (push (cons "TAGS" value) props))
- (when (and (or (not specific) (string= specific "ALLTAGS"))
- (setq value (org-get-tags-at)))
- (push (cons "ALLTAGS" (concat ":" (mapconcat 'identity value ":")
- ":"))
- props))
- (when (or (not specific) (string= specific "BLOCKED"))
- (push (cons "BLOCKED" (if (org-entry-blocked-p) "t" "")) props))
- (when (or (not specific)
- (member specific
- '("SCHEDULED" "DEADLINE" "CLOCK" "CLOSED"
- "TIMESTAMP" "TIMESTAMP_IA")))
- (catch 'match
- (while (and (re-search-forward org-maybe-keyword-time-regexp end t)
- (not (text-property-any 0 (length (match-string 0))
- 'face 'font-lock-comment-face
- (match-string 0))))
- (setq key (if (match-end 1)
- (substring (org-match-string-no-properties 1)
- 0 -1))
- string (if (equal key clockstr)
- (org-trim
- (buffer-substring-no-properties
- (match-beginning 3) (goto-char
- (point-at-eol))))
- (substring (org-match-string-no-properties 3)
- 1 -1)))
- ;; Get the correct property name from the key. This is
- ;; necessary if the user has configured time keywords.
- (setq key1 (concat key ":"))
- (cond
- ((not key)
- (setq key
- (if (= (char-after (match-beginning 3)) ?\[)
- "TIMESTAMP_IA" "TIMESTAMP")))
- ((equal key1 org-scheduled-string) (setq key "SCHEDULED"))
- ((equal key1 org-deadline-string) (setq key "DEADLINE"))
- ((equal key1 org-closed-string) (setq key "CLOSED"))
- ((equal key1 org-clock-string) (setq key "CLOCK")))
- (if (and specific (equal key specific) (not (equal key "CLOCK")))
- (progn
- (push (cons key string) props)
- ;; no need to search further if match is found
- (throw 'match t))
- (when (or (equal key "CLOCK") (not (assoc key props)))
- (push (cons key string) props)))))))
-
- (when (memq which '(all standard))
- ;; Get the standard properties, like :PROP: ...
- (setq range (org-get-property-block beg end))
- (when range
- (goto-char (car range))
- (while (re-search-forward org-property-re
- (cdr range) t)
- (setq key (org-match-string-no-properties 2)
- value (org-trim (or (org-match-string-no-properties 3) "")))
- (unless (member key excluded)
- (push (cons key (or value "")) props)))))
- (if clocksum
- (push (cons "CLOCKSUM"
- (org-columns-number-to-string (/ (float clocksum) 60.)
- 'add_times))
- props))
- (if clocksumt
- (push (cons "CLOCKSUM_T"
- (org-columns-number-to-string (/ (float clocksumt) 60.)
- 'add_times))
- props))
- (unless (assoc "CATEGORY" props)
- (push (cons "CATEGORY" (org-get-category)) props))
- (append sum-props (nreverse props)))))))
+`special' or `standard', only get that subclass. If WHICH is
+a string, only get that property.
+
+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)))
+ (catch 'exit
+ (let* ((beg (point))
+ (specific (and (stringp which) (upcase which)))
+ (which (cond ((not specific) which)
+ ((member specific org-special-properties) 'special)
+ (t 'standard)))
+ props)
+ ;; Get the special properties, like TODO and TAGS.
+ (when (memq which '(nil all special))
+ (when (or (not specific) (string= specific "CLOCKSUM"))
+ (let ((clocksum (get-text-property (point) :org-clock-minutes)))
+ (when clocksum
+ (push (cons "CLOCKSUM"
+ (org-minutes-to-clocksum-string clocksum))
+ props)))
+ (when specific (throw 'exit props)))
+ (when (or (not specific) (string= specific "CLOCKSUM_T"))
+ (let ((clocksumt (get-text-property (point)
+ :org-clock-minutes-today)))
+ (when clocksumt
+ (push (cons "CLOCKSUM_T"
+ (org-minutes-to-clocksum-string clocksumt))
+ props)))
+ (when specific (throw 'exit props)))
+ (when (or (not specific) (string= specific "ITEM"))
+ (let ((case-fold-search nil))
+ (when (looking-at org-complex-heading-regexp)
+ (push (cons "ITEM"
+ (let ((title (match-string-no-properties 4)))
+ (if (org-string-nw-p title)
+ (org-remove-tabs title)
+ "")))
+ props)))
+ (when specific (throw 'exit props)))
+ (when (or (not specific) (string= specific "TODO"))
+ (let ((case-fold-search nil))
+ (when (and (looking-at org-todo-line-regexp) (match-end 2))
+ (push (cons "TODO" (match-string-no-properties 2)) props)))
+ (when specific (throw 'exit props)))
+ (when (or (not specific) (string= specific "PRIORITY"))
+ (push (cons "PRIORITY"
+ (if (looking-at org-priority-regexp)
+ (match-string-no-properties 2)
+ (char-to-string org-default-priority)))
+ props)
+ (when specific (throw 'exit props)))
+ (when (or (not specific) (string= specific "FILE"))
+ (push (cons "FILE" (buffer-file-name (buffer-base-buffer)))
+ props)
+ (when specific (throw 'exit props)))
+ (when (or (not specific) (string= specific "TAGS"))
+ (let ((value (org-string-nw-p (org-get-tags-string))))
+ (when value (push (cons "TAGS" value) props)))
+ (when specific (throw 'exit props)))
+ (when (or (not specific) (string= specific "ALLTAGS"))
+ (let ((value (org-get-tags-at)))
+ (when value
+ (push (cons "ALLTAGS"
+ (format ":%s:" (mapconcat #'identity value ":")))
+ props)))
+ (when specific (throw 'exit props)))
+ (when (or (not specific) (string= specific "BLOCKED"))
+ (push (cons "BLOCKED" (if (org-entry-blocked-p) "t" "")) props)
+ (when specific (throw 'exit props)))
+ (when (or (not specific)
+ (member specific '("CLOSED" "DEADLINE" "SCHEDULED")))
+ (forward-line)
+ (when (looking-at-p org-planning-line-re)
+ (end-of-line)
+ (let ((bol (line-beginning-position))
+ ;; Backward compatibility: time keywords used to
+ ;; be configurable (before 8.3). Make sure we
+ ;; get the correct keyword.
+ (key-assoc `(("CLOSED" . ,org-closed-string)
+ ("DEADLINE" . ,org-deadline-string)
+ ("SCHEDULED" . ,org-scheduled-string))))
+ (dolist (pair (if specific (list (assoc specific key-assoc))
+ key-assoc))
+ (save-excursion
+ (when (search-backward (cdr pair) bol t)
+ (goto-char (match-end 0))
+ (skip-chars-forward " \t")
+ (and (looking-at org-ts-regexp-both)
+ (push (cons (car pair)
+ (match-string-no-properties 0))
+ props)))))))
+ (when specific (throw 'exit props)))
+ (when (or (not specific)
+ (member specific '("TIMESTAMP" "TIMESTAMP_IA")))
+ (let ((find-ts
+ (lambda (end ts)
+ ;; Fix next time-stamp before END. TS is the
+ ;; list of time-stamps found so far.
+ (let ((ts ts)
+ (regexp (cond
+ ((string= specific "TIMESTAMP")
+ org-ts-regexp)
+ ((string= specific "TIMESTAMP_IA")
+ org-ts-regexp-inactive)
+ ((assoc "TIMESTAMP_IA" ts)
+ org-ts-regexp)
+ ((assoc "TIMESTAMP" ts)
+ org-ts-regexp-inactive)
+ (t org-ts-regexp-both))))
+ (catch 'next
+ (while (re-search-forward regexp end t)
+ (backward-char)
+ (let ((object (org-element-context)))
+ ;; Accept to match timestamps in node
+ ;; properties, too.
+ (when (memq (org-element-type object)
+ '(node-property timestamp))
+ (let ((type
+ (org-element-property :type object)))
+ (cond
+ ((and (memq type '(active active-range))
+ (not (equal specific "TIMESTAMP_IA")))
+ (unless (assoc "TIMESTAMP" ts)
+ (push (cons "TIMESTAMP"
+ (org-element-property
+ :raw-value object))
+ ts)
+ (when specific (throw 'exit ts))))
+ ((and (memq type '(inactive inactive-range))
+ (not (string= specific "TIMESTAMP")))
+ (unless (assoc "TIMESTAMP_IA" ts)
+ (push (cons "TIMESTAMP_IA"
+ (org-element-property
+ :raw-value object))
+ ts)
+ (when specific (throw 'exit ts))))))
+ ;; Both timestamp types are found,
+ ;; move to next part.
+ (when (= (length ts) 2) (throw 'next ts)))))
+ ts)))))
+ (goto-char beg)
+ ;; First look for timestamps within headline.
+ (let ((ts (funcall find-ts (line-end-position) nil)))
+ (if (= (length ts) 2) (setq props (nconc ts props))
+ ;; Then find timestamps in the section, skipping
+ ;; planning line.
+ (let ((end (save-excursion (outline-next-heading))))
+ (forward-line)
+ (when (looking-at-p org-planning-line-re) (forward-line))
+ (setq props (nconc (funcall find-ts end ts) props))))))))
+ ;; Get the standard properties, like :PROP:.
+ (when (memq which '(nil all standard))
+ ;; If we are looking after a specific property, delegate
+ ;; to `org-entry-get', which is faster. However, make an
+ ;; exception for "CATEGORY", since it can be also set
+ ;; through keywords (i.e. #+CATEGORY).
+ (if (and specific (not (equal specific "CATEGORY")))
+ (let ((value (org-entry-get beg specific nil t)))
+ (throw 'exit (and value (list (cons specific value)))))
+ (let ((range (org-get-property-block beg)))
+ (when range
+ (let ((end (cdr range)) seen-base)
+ (goto-char (car range))
+ ;; Unlike to `org--update-property-plist', we
+ ;; handle the case where base values is found
+ ;; after its extension. We also forbid standard
+ ;; properties to be named as special properties.
+ (while (re-search-forward org-property-re end t)
+ (let* ((key (upcase (match-string-no-properties 2)))
+ (extendp (string-match-p "\\+\\'" key))
+ (key-base (if extendp (substring key 0 -1) key))
+ (value (match-string-no-properties 3)))
+ (cond
+ ((member-ignore-case key-base org-special-properties))
+ (extendp
+ (setq props
+ (org--update-property-plist key value props)))
+ ((member key seen-base))
+ (t (push key seen-base)
+ (let ((p (assoc-string key props t)))
+ (if p (setcdr p (concat value " " (cdr p)))
+ (push (cons key value) props))))))))))))
+ (unless (assoc "CATEGORY" props)
+ (push (cons "CATEGORY" (org-get-category beg)) props)
+ (when (string= specific "CATEGORY") (throw 'exit props)))
+ ;; Return value.
+ props)))))
+
+(defun org--property-local-values (property literal-nil)
+ "Return value for PROPERTY in current entry.
+Value is a list whose car is the base value for PROPERTY and cdr
+a list of accumulated values. Return nil if neither is found in
+the entry. Also return nil when PROPERTY is set to \"nil\",
+unless LITERAL-NIL is non-nil."
+ (let ((range (org-get-property-block)))
+ (when range
+ (goto-char (car range))
+ (let* ((case-fold-search t)
+ (end (cdr range))
+ (value
+ ;; Base value.
+ (save-excursion
+ (let ((v (and (re-search-forward
+ (org-re-property property nil t) end t)
+ (match-string-no-properties 3))))
+ (list (if literal-nil v (org-not-nil v)))))))
+ ;; Find additional values.
+ (let* ((property+ (org-re-property (concat property "+") nil t)))
+ (while (re-search-forward property+ end t)
+ (push (match-string-no-properties 3) value)))
+ ;; 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.
+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."
+ (let ((global
+ (cdr (or (assoc-string property org-file-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))))
(defun org-entry-get (pom property &optional inherit literal-nil)
"Get value of PROPERTY for entry or content at point-or-marker POM.
-If INHERIT is non-nil and the entry does not have the property,
-then also check higher levels of the hierarchy.
-If INHERIT is the symbol `selective', use inheritance only if the setting
-in `org-use-property-inheritance' selects PROPERTY for inheritance.
-If the property is present but empty, the return value is the empty string.
-If the property is not present at all, nil is returned.
-
-Return the value as a string.
-If LITERAL-NIL is set, return the string value \"nil\" as a string,
-do not interpret it as the list atom nil. This is used for inheritance
-when a \"nil\" value can supersede a non-nil value higher up the hierarchy."
+If INHERIT is non-nil and the entry does not have the property,
+then also check higher levels of the hierarchy. If INHERIT is
+the symbol `selective', use inheritance only if the setting in
+`org-use-property-inheritance' selects PROPERTY for inheritance.
+
+If the property is present but empty, the return value is the
+empty string. If the property is not present at all, nil is
+returned. In any other case, return the value as a string.
+Search is case-insensitive.
+
+If LITERAL-NIL is set, return the string value \"nil\" as
+a string, do not interpret it as the list atom nil. This is used
+for inheritance when a \"nil\" value can supersede a non-nil
+value higher up the hierarchy."
(org-with-point-at pom
- (if (and inherit (if (eq inherit 'selective)
- (org-property-inherit-p property)
- t))
- (org-entry-get-with-inheritance property literal-nil)
- (if (member property org-special-properties)
- ;; We need a special property. Use `org-entry-properties'
- ;; to retrieve it, but specify the wanted property
- (cdr (assoc property (org-entry-properties nil 'special property)))
- (org-with-wide-buffer
- (let ((range (org-get-property-block)))
- (when (and range (not (eq (car range) (cdr range)))
- (save-excursion
- (goto-char (car range))
- (re-search-forward
- (concat (org-re-property property) "\\|"
- (org-re-property (concat property "+")))
- (cdr range) t)))
- (let* ((props
- (list (or (assoc property org-file-properties)
- (assoc property org-global-properties)
- (assoc property org-global-properties-fixed))))
- (ap (lambda (key)
- (when (re-search-forward
- (org-re-property key) (cdr range) t)
- (setq props
- (org-update-property-plist
- key
- (if (match-end 3)
- (org-match-string-no-properties 3) "")
- props)))))
- val)
- (goto-char (car range))
- (funcall ap property)
- (goto-char (car range))
- (while (funcall ap (concat property "+")))
- (setq val (cdr (assoc property props)))
- (when val (if literal-nil val (org-not-nil val)))))))))))
+ (cond
+ ((member-ignore-case property (cons "CATEGORY" org-special-properties))
+ ;; We need a special property. Use `org-entry-properties' to
+ ;; retrieve it, but specify the wanted property.
+ (cdr (assoc-string property (org-entry-properties nil property))))
+ ((and inherit
+ (or (not (eq inherit 'selective)) (org-property-inherit-p property)))
+ (org-entry-get-with-inheritance property literal-nil))
+ (t
+ (let* ((local (org--property-local-values property literal-nil))
+ (value (and local (mapconcat #'identity (delq nil local) " "))))
+ (if literal-nil value (org-not-nil value)))))))
(defun org-property-or-variable-value (var &optional inherit)
"Check if there is a property fixing the value of VAR.
@@ -15430,26 +16012,26 @@ If yes, return this value. If not, return the current value of the variable."
(read prop)
(symbol-value var))))
-(defun org-entry-delete (pom property &optional delete-empty-drawer)
- "Delete the property PROPERTY from entry at point-or-marker POM.
-When optional argument DELETE-EMPTY-DRAWER is a string, it defines
-an empty drawer to delete."
+(defun org-entry-delete (pom property)
+ "Delete PROPERTY from entry at point-or-marker POM.
+Accumulated properties, i.e. PROPERTY+, are also removed. Return
+non-nil when a property was removed."
(org-with-point-at pom
- (if (member property org-special-properties)
- nil ; cannot delete these properties.
- (let ((range (org-get-property-block)))
- (if (and range
- (goto-char (car range))
- (re-search-forward
- (org-re-property property nil t)
- (cdr range) t))
- (progn
- (delete-region (match-beginning 0) (1+ (point-at-eol)))
- (and delete-empty-drawer
- (org-remove-empty-drawer-at
- delete-empty-drawer (car range)))
- t)
- nil)))))
+ (pcase (org-get-property-block)
+ (`(,begin . ,origin)
+ (let* ((end (copy-marker origin))
+ (re (org-re-property
+ (concat (regexp-quote property) "\\+?") t t)))
+ (goto-char begin)
+ (while (re-search-forward re end t)
+ (delete-region (match-beginning 0) (line-beginning-position 2)))
+ ;; If drawer is empty, remove it altogether.
+ (when (= begin end)
+ (delete-region (line-beginning-position 0)
+ (line-beginning-position 2)))
+ ;; Return non-nil if some property was removed.
+ (prog1 (/= end origin) (set-marker end nil))))
+ (_ nil))))
;; Multi-values properties are properties that contain multiple values
;; These values are assumed to be single words, separated by whitespace.
@@ -15526,24 +16108,29 @@ If the value found is \"nil\", return nil to show that the property
should be considered as undefined (this is the meaning of nil here).
However, if LITERAL-NIL is set, return the string value \"nil\" instead."
(move-marker org-entry-property-inherited-from nil)
- (let (tmp)
- (save-excursion
- (save-restriction
- (widen)
- (catch 'ex
- (while t
- (when (setq tmp (org-entry-get nil property nil literal-nil))
- (or (ignore-errors (org-back-to-heading t))
- (goto-char (point-min)))
- (move-marker org-entry-property-inherited-from (point))
- (throw 'ex tmp))
- (or (ignore-errors (org-up-heading-safe))
- (throw 'ex nil))))))
- (setq tmp (or tmp
- (cdr (assoc property org-file-properties))
- (cdr (assoc property org-global-properties))
- (cdr (assoc property org-global-properties-fixed))))
- (if literal-nil tmp (org-not-nil tmp))))
+ (org-with-wide-buffer
+ (let (value)
+ (catch 'exit
+ (while t
+ (let ((v (org--property-local-values property literal-nil)))
+ (when v
+ (setq value
+ (concat (mapconcat #'identity (delq nil v) " ")
+ (and value " ")
+ value)))
+ (cond
+ ((car v)
+ (org-back-to-heading t)
+ (move-marker org-entry-property-inherited-from (point))
+ (throw 'exit nil))
+ ((org-up-heading-safe))
+ (t
+ (let ((global (org--property-global-value property literal-nil)))
+ (cond ((not global))
+ (value (setq value (concat global " " value)))
+ (t (setq value global))))
+ (throw 'exit nil))))))
+ (if literal-nil value (org-not-nil value)))))
(defvar org-property-changed-functions nil
"Hook called when the value of a property has changed.
@@ -15552,177 +16139,188 @@ and the new value.")
(defun org-entry-put (pom property value)
"Set PROPERTY to VALUE for entry at point-or-marker POM.
-If the value is nil, it is converted to the empty string.
-If it is not a string, an error is raised."
+
+If the value is nil, it is converted to the empty string. If it
+is not a string, an error is raised. Also raise an error on
+invalid property names.
+
+PROPERTY can be any regular property (see
+`org-special-properties'). It can also be \"TODO\",
+\"PRIORITY\", \"SCHEDULED\" and \"DEADLINE\".
+
+For the last two properties, VALUE may have any of the special
+values \"earlier\" and \"later\". The function then increases or
+decreases scheduled or deadline date by one day."
(cond ((null value) (setq value ""))
- ((not (stringp value))
- (error "Properties values should be strings.")))
+ ((not (stringp value)) (error "Properties values should be strings"))
+ ((not (org--valid-property-p property))
+ (user-error "Invalid property name: \"%s\"" property)))
(org-with-point-at pom
- (org-back-to-heading t)
- (let ((beg (point)) (end (save-excursion (outline-next-heading) (point)))
- range)
+ (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)))
+ (let ((beg (point)))
(cond
((equal property "TODO")
- (when (and (string-match "\\S-" value)
- (not (member value org-todo-keywords-1)))
- (user-error "\"%s\" is not a valid TODO state" value))
- (if (or (not value)
- (not (string-match "\\S-" value)))
- (setq value 'none))
+ (cond ((not (org-string-nw-p value)) (setq value 'none))
+ ((not (member value org-todo-keywords-1))
+ (user-error "\"%s\" is not a valid TODO state" value)))
(org-todo value)
(org-set-tags nil 'align))
((equal property "PRIORITY")
- (org-priority (if (and value (string-match "\\S-" value))
- (string-to-char value) ?\ ))
+ (org-priority (if (org-string-nw-p value) (string-to-char value) ?\s))
(org-set-tags nil 'align))
- ((equal property "CLOCKSUM")
- (if (not (re-search-forward
- (concat org-clock-string ".*\\]--\\(\\[[^]]+\\]\\)") nil t))
- (error "Cannot find a clock log")
- (goto-char (- (match-end 1) 2))
- (cond
- ((eq value 'earlier) (org-timestamp-down))
- ((eq value 'later) (org-timestamp-up)))
- (org-clock-sum-current-item)))
((equal property "SCHEDULED")
- (if (re-search-forward org-scheduled-time-regexp end t)
- (cond
- ((eq value 'earlier) (org-timestamp-change -1 'day))
- ((eq value 'later) (org-timestamp-change 1 'day))
- (t (call-interactively 'org-schedule)))
- (call-interactively 'org-schedule)))
+ (forward-line)
+ (if (and (looking-at-p org-planning-line-re)
+ (re-search-forward
+ org-scheduled-time-regexp (line-end-position) t))
+ (cond ((string= value "earlier") (org-timestamp-change -1 'day))
+ ((string= value "later") (org-timestamp-change 1 'day))
+ ((string= value "") (org-schedule '(4)))
+ (t (org-schedule nil value)))
+ (if (member value '("earlier" "later" ""))
+ (call-interactively #'org-schedule)
+ (org-schedule nil value))))
((equal property "DEADLINE")
- (if (re-search-forward org-deadline-time-regexp end t)
- (cond
- ((eq value 'earlier) (org-timestamp-change -1 'day))
- ((eq value 'later) (org-timestamp-change 1 'day))
- (t (call-interactively 'org-deadline)))
- (call-interactively 'org-deadline)))
+ (forward-line)
+ (if (and (looking-at-p org-planning-line-re)
+ (re-search-forward
+ org-deadline-time-regexp (line-end-position) t))
+ (cond ((string= value "earlier") (org-timestamp-change -1 'day))
+ ((string= value "later") (org-timestamp-change 1 'day))
+ ((string= value "") (org-deadline '(4)))
+ (t (org-deadline nil value)))
+ (if (member value '("earlier" "later" ""))
+ (call-interactively #'org-deadline)
+ (org-deadline nil value))))
((member property org-special-properties)
- (error "The %s property can not yet be set with `org-entry-put'"
- property))
- (t ; a non-special property
- (let ((buffer-invisibility-spec (org-inhibit-invisibility))) ; Emacs 21
- (setq range (org-get-property-block beg end 'force))
+ (error "The %s property cannot be set with `org-entry-put'" property))
+ (t
+ (let* ((range (org-get-property-block beg 'force))
+ (end (cdr range))
+ (case-fold-search t))
(goto-char (car range))
- (if (re-search-forward
- (org-re-property property nil t) (cdr range) t)
- (progn
- (delete-region (match-beginning 0) (match-end 0))
- (goto-char (match-beginning 0)))
- (goto-char (cdr range))
+ (if (re-search-forward (org-re-property property nil t) end t)
+ (progn (delete-region (match-beginning 0) (match-end 0))
+ (goto-char (match-beginning 0)))
+ (goto-char end)
(insert "\n")
- (backward-char 1)
- (org-indent-line))
+ (backward-char))
(insert ":" property ":")
- (and value (insert " " value))
+ (when value (insert " " value))
(org-indent-line)))))
(run-hook-with-args 'org-property-changed-functions property value)))
-(defun org-buffer-property-keys (&optional include-specials include-defaults include-columns)
+(defun org-buffer-property-keys
+ (&optional specials defaults columns ignore-malformed)
"Get all property keys in the current buffer.
-With INCLUDE-SPECIALS, also list the special properties that reflect things
-like tags and TODO state.
-With INCLUDE-DEFAULTS, also include properties that has special meaning
-internally: ARCHIVE, CATEGORY, SUMMARY, DESCRIPTION, LOCATION, and LOGGING
-and others.
-With INCLUDE-COLUMNS, also include property names given in COLUMN
-formats in the current buffer."
- (let (rtn range cfmt s p)
- (save-excursion
- (save-restriction
- (widen)
- (goto-char (point-min))
- (while (re-search-forward org-property-start-re nil t)
- (setq range (org-get-property-block))
- (goto-char (car range))
- (while (re-search-forward org-property-re
- (cdr range) t)
- (add-to-list 'rtn (org-match-string-no-properties 2)))
- (outline-next-heading))))
- (when include-specials
- (setq rtn (append org-special-properties rtn)))
+When SPECIALS is non-nil, also list the special properties that
+reflect things like tags and TODO state.
- (when include-defaults
- (mapc (lambda (x) (add-to-list 'rtn x)) org-default-properties)
- (add-to-list 'rtn org-effort-property))
+When DEFAULTS is non-nil, also include properties that has
+special meaning internally: ARCHIVE, CATEGORY, SUMMARY,
+DESCRIPTION, LOCATION, and LOGGING and others.
- (when include-columns
- (save-excursion
- (save-restriction
- (widen)
- (goto-char (point-min))
- (while (re-search-forward
- "^\\(#\\+COLUMNS:\\|[ \t]*:COLUMNS:\\)[ \t]*\\(.*\\)"
- nil t)
- (setq cfmt (match-string 2) s 0)
- (while (string-match (org-re "%[0-9]*\\([-[:alnum:]_]+\\)")
- cfmt s)
- (setq s (match-end 0)
- p (match-string 1 cfmt))
- (unless (or (equal p "ITEM")
- (member p org-special-properties))
- (add-to-list 'rtn (match-string 1 cfmt))))))))
-
- (sort rtn (lambda (a b) (string< (upcase a) (upcase b))))))
+When COLUMNS in non-nil, also include property names given in
+COLUMN formats in the current buffer.
+
+When IGNORE-MALFORMED is non-nil, malformed drawer repair will not be
+automatically performed, such drawers will be silently ignored."
+ (let ((case-fold-search t)
+ (props (append
+ (and specials org-special-properties)
+ (and defaults (cons org-effort-property org-default-properties))
+ nil)))
+ (org-with-wide-buffer
+ (goto-char (point-min))
+ (while (re-search-forward org-property-start-re nil t)
+ (let ((range (org-get-property-block)))
+ (catch 'skip
+ (unless range
+ (when (and (not ignore-malformed)
+ (not (org-before-first-heading-p))
+ (y-or-n-p (format "Malformed drawer at %d, repair?"
+ (line-beginning-position))))
+ (org-get-property-block nil t))
+ (throw 'skip nil))
+ (goto-char (car range))
+ (let ((begin (car range))
+ (end (cdr range)))
+ ;; Make sure that found property block is not located
+ ;; before current point, as it would generate an infloop.
+ ;; It can happen, for example, in the following
+ ;; situation:
+ ;;
+ ;; * Headline
+ ;; :PROPERTIES:
+ ;; ...
+ ;; :END:
+ ;; *************** Inlinetask
+ ;; #+BEGIN_EXAMPLE
+ ;; :PROPERTIES:
+ ;; #+END_EXAMPLE
+ ;;
+ (if (< begin (point)) (throw 'skip nil) (goto-char begin))
+ (while (< (point) end)
+ (let ((p (progn (looking-at org-property-re)
+ (match-string-no-properties 2))))
+ ;; Only add true property name, not extension symbol.
+ (push (if (not (string-match-p "\\+\\'" p)) p
+ (substring p 0 -1))
+ props))
+ (forward-line))))
+ (outline-next-heading)))
+ (when columns
+ (goto-char (point-min))
+ (while (re-search-forward "^[ \t]*\\(?:#\\+\\|:\\)COLUMNS:" nil t)
+ (let ((element (org-element-at-point)))
+ (when (memq (org-element-type element) '(keyword node-property))
+ (let ((value (org-element-property :value element))
+ (start 0))
+ (while (string-match "%[0-9]*\\(\\S-+\\)" value start)
+ (setq start (match-end 0))
+ (let ((p (match-string-no-properties 1 value)))
+ (unless (member-ignore-case p org-special-properties)
+ (push p props))))))))))
+ (sort (delete-dups props) (lambda (a b) (string< (upcase a) (upcase b))))))
(defun org-property-values (key)
- "Return a list of all values of property KEY in the current buffer."
- (save-excursion
- (save-restriction
- (widen)
- (goto-char (point-min))
- (let ((re (org-re-property key))
- values)
- (while (re-search-forward re nil t)
- (add-to-list 'values (org-trim (match-string 3))))
- (delete "" values)))))
+ "List all non-nil values of property KEY in current buffer."
+ (org-with-wide-buffer
+ (goto-char (point-min))
+ (let ((case-fold-search t)
+ (re (org-re-property key))
+ values)
+ (while (re-search-forward re nil t)
+ (push (org-entry-get (point) key) values))
+ (delete-dups values))))
(defun org-insert-property-drawer ()
"Insert a property drawer into the current entry."
- (org-back-to-heading t)
- (looking-at org-outline-regexp)
- (let ((indent (if org-adapt-indentation
- (- (match-end 0) (match-beginning 0))
- 0))
- (beg (point))
- (re (concat "^[ \t]*" org-keyword-time-regexp))
- end hiddenp)
- (outline-next-heading)
- (setq end (point))
- (goto-char beg)
- (while (re-search-forward re end t))
- (setq hiddenp (outline-invisible-p))
- (end-of-line 1)
- (and (equal (char-after) ?\n) (forward-char 1))
- (while (looking-at "^[ \t]*\\(:CLOCK:\\|:LOGBOOK:\\|CLOCK:\\|:END:\\)")
- (if (member (match-string 1) '("CLOCK:" ":END:"))
- ;; just skip this line
- (beginning-of-line 2)
- ;; Drawer start, find the end
- (re-search-forward "^\\*+ \\|^[ \t]*:END:" nil t)
- (beginning-of-line 1)))
- (org-skip-over-state-notes)
- (skip-chars-backward " \t\n\r")
- (if (and (eq (char-before) ?*) (not (eq (char-after) ?\n)))
- (forward-char 1))
- (goto-char (point-at-eol))
- (let ((inhibit-read-only t)) (insert "\n:PROPERTIES:\n:END:"))
- (beginning-of-line 0)
- (org-indent-to-column indent)
- (beginning-of-line 2)
- (org-indent-to-column indent)
- (beginning-of-line 0)
- (if hiddenp
- (save-excursion
- (org-back-to-heading t)
- (hide-entry))
- (org-flag-drawer t))))
+ (org-with-wide-buffer
+ (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))
+ (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)))
+ (inhibit-read-only t))
+ (insert "\n:PROPERTIES:\n:END:")
+ (when (eobp) (insert "\n"))
+ (org-indent-region begin (point))))))
(defun org-insert-drawer (&optional arg drawer)
"Insert a drawer at point.
+When optional argument ARG is non-nil, insert a property drawer.
+
Optional argument DRAWER, when non-nil, is a string representing
drawer's name. Otherwise, the user is prompted for a name.
@@ -15731,23 +16329,14 @@ instead.
Point is left between drawer's boundaries."
(interactive "P")
- (let* ((logbook (if (stringp org-log-into-drawer) org-log-into-drawer
- "LOGBOOK"))
- ;; SYSTEM-DRAWERS is a list of drawer names that are used
- ;; internally by Org. They are meant to be inserted
- ;; automatically.
- (system-drawers `("CLOCK" ,logbook "PROPERTIES"))
- ;; Remove system drawers from list. Note: For some reason,
- ;; `org-completing-read' ignores the predicate while
- ;; `completing-read' handles it fine.
- (drawer (if arg "PROPERTIES"
- (or drawer
- (completing-read
- "Drawer: " org-drawers
- (lambda (d) (not (member d system-drawers))))))))
+ (let* ((drawer (if arg "PROPERTIES"
+ (or drawer (read-from-minibuffer "Drawer: ")))))
(cond
;; With C-u, fall back on `org-insert-property-drawer'
(arg (org-insert-property-drawer))
+ ;; Check validity of suggested drawer's name.
+ ((not (string-match-p org-drawer-regexp (format ":%s:" drawer)))
+ (user-error "Invalid drawer name"))
;; With an active region, insert a drawer at point.
((not (org-region-active-p))
(progn
@@ -15813,38 +16402,25 @@ This is computed according to `org-property-set-functions-alist'."
(funcall set-function prompt allowed nil
(not (get-text-property 0 'org-unrestricted
(caar allowed))))
- (let (org-completion-use-ido org-completion-use-iswitchb)
- (funcall set-function prompt
- (mapcar 'list (org-property-values property))
- nil nil "" nil cur)))))
+ (funcall set-function prompt
+ (mapcar 'list (org-property-values property))
+ nil nil "" nil cur))))
(org-trim val)))
(defvar org-last-set-property nil)
(defvar org-last-set-property-value nil)
(defun org-read-property-name ()
"Read a property name."
- (let* ((completion-ignore-case t)
- (keys (org-buffer-property-keys nil t t))
- (default-prop (or (save-excursion
- (save-match-data
- (beginning-of-line)
- (and (looking-at "^\\s-*:\\([^:\n]+\\):")
- (null (string= (match-string 1) "END"))
- (match-string 1))))
- org-last-set-property))
- (property (org-icompleting-read
- (concat "Property"
- (if default-prop (concat " [" default-prop "]") "")
- ": ")
- (mapcar 'list keys)
- nil nil nil nil
- default-prop)))
- (if (member property keys)
- property
- (or (cdr (assoc (downcase property)
- (mapcar (lambda (x) (cons (downcase x) x))
- keys)))
- property))))
+ (let ((completion-ignore-case t)
+ (default-prop (or (and (org-at-property-p)
+ (match-string-no-properties 2))
+ org-last-set-property)))
+ (org-completing-read
+ (concat "Property"
+ (if default-prop (concat " [" default-prop "]") "")
+ ": ")
+ (mapcar #'list (org-buffer-property-keys nil t t))
+ nil nil nil nil default-prop)))
(defun org-set-property-and-value (use-last)
"Allow to set [PROPERTY]: [value] direction from prompt.
@@ -15865,26 +16441,52 @@ When use-default, don't even ask, just use the last
(defun org-set-property (property value)
"In the current entry, set PROPERTY to VALUE.
+
When called interactively, this will prompt for a property name, offering
completion on existing and default properties. And then it will prompt
for a value, offering completion either on allowed values (via an inherited
xxx_ALL property) or on existing values in other instances of this property
-in the current file."
+in the current file.
+
+Throw an error when trying to set a property with an invalid name."
(interactive (list nil nil))
- (let* ((property (or property (org-read-property-name)))
- (value (or value (org-read-property-value property)))
- (fn (cdr (assoc property org-properties-postprocess-alist))))
- (setq org-last-set-property property)
- (setq org-last-set-property-value (concat property ": " value))
- ;; Possibly postprocess the inserted value:
- (when fn (setq value (funcall fn value)))
- (unless (equal (org-entry-get nil property) value)
- (org-entry-put nil property value))))
-
-(defun org-delete-property (property &optional delete-empty-drawer)
- "In the current entry, delete PROPERTY.
-When optional argument DELETE-EMPTY-DRAWER is a string, it defines
-an empty drawer to delete."
+ (let ((property (or property (org-read-property-name))))
+ ;; `org-entry-put' also makes the following check, but this one
+ ;; avoids polluting `org-last-set-property' and
+ ;; `org-last-set-property-value' needlessly.
+ (unless (org--valid-property-p property)
+ (user-error "Invalid property name: \"%s\"" property))
+ (let ((value (or value (org-read-property-value property)))
+ (fn (cdr (assoc-string property org-properties-postprocess-alist t))))
+ (setq org-last-set-property property)
+ (setq org-last-set-property-value (concat property ": " value))
+ ;; Possibly postprocess the inserted value:
+ (when fn (setq value (funcall fn value)))
+ (unless (equal (org-entry-get nil property) value)
+ (org-entry-put nil property value)))))
+
+(defun org-find-property (property &optional value)
+ "Find first entry in buffer that sets PROPERTY.
+
+When optional argument VALUE is non-nil, only consider an entry
+if it contains PROPERTY set to this value. If PROPERTY should be
+explicitly set to nil, use string \"nil\" for VALUE.
+
+Return position where the entry begins, or nil if there is no
+such entry. If narrowing is in effect, only search the visible
+part of the buffer."
+ (save-excursion
+ (goto-char (point-min))
+ (let ((case-fold-search t)
+ (re (org-re-property property nil (not value) value)))
+ (catch 'exit
+ (while (re-search-forward re nil t)
+ (when (if value (org-at-property-p)
+ (org-entry-get (point) property nil t))
+ (throw 'exit (progn (org-back-to-heading t) (point)))))))))
+
+(defun org-delete-property (property)
+ "In the current entry, delete PROPERTY."
(interactive
(let* ((completion-ignore-case t)
(cat (org-entry-get (point) "CATEGORY"))
@@ -15892,33 +16494,30 @@ an empty drawer to delete."
(props (if cat props0
(delete `("CATEGORY" . ,(org-get-category)) props0)))
(prop (if (< 1 (length props))
- (org-icompleting-read "Property: " props nil t)
+ (completing-read "Property: " props nil t)
(caar props))))
(list prop)))
(if (not property)
(message "No property to delete in this entry")
- (org-entry-delete nil property delete-empty-drawer)
+ (org-entry-delete nil property)
(message "Property \"%s\" deleted" property)))
(defun org-delete-property-globally (property)
- "Remove PROPERTY globally, from all entries."
+ "Remove PROPERTY globally, from all entries.
+This function ignores narrowing, if any."
(interactive
(let* ((completion-ignore-case t)
- (prop (org-icompleting-read
+ (prop (completing-read
"Globally remove property: "
- (mapcar 'list (org-buffer-property-keys)))))
+ (mapcar #'list (org-buffer-property-keys)))))
(list prop)))
- (save-excursion
- (save-restriction
- (widen)
- (goto-char (point-min))
- (let ((cnt 0))
- (while (re-search-forward
- (org-re-property property)
- nil t)
- (setq cnt (1+ cnt))
- (delete-region (match-beginning 0) (1+ (point-at-eol))))
- (message "Property \"%s\" removed from %d entries" property cnt)))))
+ (org-with-wide-buffer
+ (goto-char (point-min))
+ (let ((count 0)
+ (re (org-re-property (concat (regexp-quote property) "\\+?") t t)))
+ (while (re-search-forward re nil t)
+ (when (org-entry-delete (point) property) (cl-incf count)))
+ (message "Property \"%s\" removed from %d entries" property count))))
(defvar org-columns-current-fmt-compiled) ; defined in org-colview.el
@@ -15929,9 +16528,9 @@ then applies it to the property in the column format's scope."
(interactive)
(unless (org-at-property-p)
(user-error "Not at a property"))
- (let ((prop (org-match-string-no-properties 2)))
+ (let ((prop (match-string-no-properties 2)))
(org-columns-get-format-and-top-level)
- (unless (nth 3 (assoc prop org-columns-current-fmt-compiled))
+ (unless (nth 3 (assoc-string prop org-columns-current-fmt-compiled t))
(user-error "No operator defined for property %s" prop))
(org-columns-compute prop)))
@@ -15958,6 +16557,7 @@ completion."
(while (>= n org-highest-priority)
(push (char-to-string n) vals)
(setq n (1- n)))))
+ ((equal property "CATEGORY"))
((member property org-special-properties))
((setq vals (run-hook-with-args-until-success
'org-property-allowed-value-functions property)))
@@ -15976,7 +16576,7 @@ completion."
(org-add-props (car vals) '(org-unrestricted t)))
(if table (mapcar 'list vals) vals)))
-(defun org-property-previous-allowed-value (&optional previous)
+(defun org-property-previous-allowed-value (&optional _previous)
"Switch to the next allowed value for this property."
(interactive)
(org-property-next-allowed-value t))
@@ -15996,21 +16596,22 @@ completion."
nval)
(unless allowed
(user-error "Allowed values for this property have not been defined"))
- (if previous (setq allowed (reverse allowed)))
- (if (member value allowed)
- (setq nval (car (cdr (member value allowed)))))
+ (when previous (setq allowed (reverse allowed)))
+ (when (member value allowed)
+ (setq nval (car (cdr (member value allowed)))))
(setq nval (or nval (car allowed)))
- (if (equal nval value)
- (user-error "Only one allowed value for this property"))
+ (when (equal nval value)
+ (user-error "Only one allowed value for this property"))
(org-at-property-p)
(replace-match (concat " :" key ": " nval) t t)
(org-indent-line)
(beginning-of-line 1)
(skip-chars-forward " \t")
(when (equal prop org-effort-property)
- (save-excursion
- (org-back-to-heading t)
- (put-text-property (point-at-bol) (point-at-eol) 'org-effort nval))
+ (org-refresh-property
+ '((effort . identity)
+ (effort-minutes . org-duration-string-to-minutes))
+ nval)
(when (string= org-clock-current-task heading)
(setq org-clock-effort nval)
(org-clock-update-mode-line)))
@@ -16035,31 +16636,28 @@ only headings."
(level 1)
(lmin 1)
(lmax 1)
- limit re end found pos heading cnt flevel)
+ end found flevel)
(unless buffer (error "File not found :%s" file))
(with-current-buffer buffer
- (save-excursion
- (save-restriction
- (widen)
- (setq limit (point-max))
- (goto-char (point-min))
- (dolist (heading path)
- (setq re (format org-complex-heading-regexp-format
- (regexp-quote heading)))
- (setq cnt 0 pos (point))
- (while (re-search-forward re end t)
- (setq level (- (match-end 1) (match-beginning 1)))
- (if (and (>= level lmin) (<= level lmax))
- (setq found (match-beginning 0) flevel level cnt (1+ cnt))))
- (when (= cnt 0) (error "Heading not found on level %d: %s"
- lmax heading))
- (when (> cnt 1) (error "Heading not unique on level %d: %s"
- lmax heading))
- (goto-char found)
- (setq lmin (1+ flevel) lmax (+ lmin (if org-odd-levels-only 1 0)))
- (setq end (save-excursion (org-end-of-subtree t t))))
- (when (org-at-heading-p)
- (point-marker)))))))
+ (org-with-wide-buffer
+ (goto-char (point-min))
+ (dolist (heading path)
+ (let ((re (format org-complex-heading-regexp-format
+ (regexp-quote heading)))
+ (cnt 0))
+ (while (re-search-forward re end t)
+ (setq level (- (match-end 1) (match-beginning 1)))
+ (when (and (>= level lmin) (<= level lmax))
+ (setq found (match-beginning 0) flevel level cnt (1+ cnt))))
+ (when (= cnt 0)
+ (error "Heading not found on level %d: %s" lmax heading))
+ (when (> cnt 1)
+ (error "Heading not unique on level %d: %s" lmax heading))
+ (goto-char found)
+ (setq lmin (1+ flevel) lmax (+ lmin (if org-odd-levels-only 1 0)))
+ (setq end (save-excursion (org-end-of-subtree t t)))))
+ (when (org-at-heading-p)
+ (point-marker))))))
(defun org-find-exact-headline-in-buffer (heading &optional buffer pos-only)
"Find node HEADING in BUFFER.
@@ -16069,24 +16667,22 @@ If POS-ONLY is set, return just the position instead of a marker.
The heading text must match exact, but it may have a TODO keyword,
a priority cookie and tags in the standard locations."
(with-current-buffer (or buffer (current-buffer))
- (save-excursion
- (save-restriction
- (widen)
- (goto-char (point-min))
- (let (case-fold-search)
- (if (re-search-forward
- (format org-complex-heading-regexp-format
- (regexp-quote heading)) nil t)
- (if pos-only
- (match-beginning 0)
- (move-marker (make-marker) (match-beginning 0)))))))))
+ (org-with-wide-buffer
+ (goto-char (point-min))
+ (let (case-fold-search)
+ (when (re-search-forward
+ (format org-complex-heading-regexp-format
+ (regexp-quote heading)) nil t)
+ (if pos-only
+ (match-beginning 0)
+ (move-marker (make-marker) (match-beginning 0))))))))
(defun org-find-exact-heading-in-directory (heading &optional dir)
"Find Org node headline HEADING in all .org files in directory DIR.
When the target headline is found, return a marker to this location."
(let ((files (directory-files (or dir default-directory)
- nil "\\`[^.#].*\\.org\\'"))
- file visiting m buffer)
+ t "\\`[^.#].*\\.org\\'"))
+ visiting m buffer)
(catch 'found
(dolist (file files)
(message "trying %s" file)
@@ -16105,19 +16701,10 @@ Return the position where this entry starts, or nil if there is no such entry."
(interactive "sID: ")
(let ((id (cond
((stringp ident) ident)
- ((symbol-name ident) (symbol-name ident))
+ ((symbolp ident) (symbol-name ident))
((numberp ident) (number-to-string ident))
- (t (error "IDENT %s must be a string, symbol or number" ident))))
- (case-fold-search nil))
- (save-excursion
- (save-restriction
- (widen)
- (goto-char (point-min))
- (when (re-search-forward
- (concat "^[ \t]*:ID:[ \t]+" (regexp-quote id) "[ \t]*$")
- nil t)
- (org-back-to-heading t)
- (point))))))
+ (t (error "IDENT %s must be a string, symbol or number" ident)))))
+ (org-with-wide-buffer (org-find-property "ID" id))))
;;;; Timestamps
@@ -16128,17 +16715,16 @@ Return the position where this entry starts, or nil if there is no such entry."
(defun org-time-stamp (arg &optional inactive)
"Prompt for a date/time and insert a time stamp.
+
If the user specifies a time like HH:MM or if this command is
called with at least one prefix argument, the time stamp contains
-the date and the time. Otherwise, only the date is be included.
+the date and the time. Otherwise, only the date is included.
-All parts of a date not specified by the user is filled in from
-the current date/time. So if you just press return without
-typing anything, the time stamp will represent the current
-date/time.
+All parts of a date not specified by the user are filled in from
+the timestamp at point, if any, or the current date/time
+otherwise.
-If there is already a timestamp at the cursor, it will be
-modified.
+If there is already a timestamp at the cursor, it is replaced.
With two universal prefix arguments, insert an active timestamp
with the current time without prompting the user.
@@ -16146,57 +16732,56 @@ with the current time without prompting the user.
When called from lisp, the timestamp is inactive if INACTIVE is
non-nil."
(interactive "P")
- (let* ((ts nil)
- (default-time
- ;; Default time is either today, or, when entering a range,
- ;; the range start.
- (if (or (and (org-at-timestamp-p t) (setq ts (match-string 0)))
- (save-excursion
- (re-search-backward
- (concat org-ts-regexp "--?-?\\=") ; 1-3 minuses
- (- (point) 20) t)))
- (apply 'encode-time (org-parse-time-string (match-string 1)))
- (current-time)))
- (default-input (and ts (org-get-compact-tod ts)))
- (repeater (save-excursion
- (save-match-data
- (beginning-of-line)
- (when (re-search-forward
- "\\([.+-]+[0-9]+[hdwmy] ?\\)+" ;;\\(?:[/ ][-+]?[0-9]+[hdwmy]\\)?\\) ?"
- (save-excursion (progn (end-of-line) (point))) t)
- (match-string 0)))))
- org-time-was-given org-end-time-was-given time)
+ (let* ((ts (cond
+ ((org-at-date-range-p t)
+ (match-string (if (< (point) (- (match-beginning 2) 2)) 1 2)))
+ ((org-at-timestamp-p t) (match-string 0))))
+ ;; Default time is either the timestamp at point or today.
+ ;; When entering a range, only the range start is considered.
+ (default-time (if (not ts) (current-time)
+ (apply #'encode-time (org-parse-time-string ts))))
+ (default-input (and ts (org-get-compact-tod ts)))
+ (repeater (and ts
+ (string-match "\\([.+-]+[0-9]+[hdwmy] ?\\)+" ts)
+ (match-string 0 ts)))
+ org-time-was-given
+ org-end-time-was-given
+ (time
+ (and (if (equal arg '(16)) (current-time)
+ ;; Preserve `this-command' and `last-command'.
+ (let ((this-command this-command)
+ (last-command last-command))
+ (org-read-date
+ arg 'totime nil nil default-time default-input
+ inactive))))))
(cond
- ((and (org-at-timestamp-p t)
- (memq last-command '(org-time-stamp org-time-stamp-inactive))
- (memq this-command '(org-time-stamp org-time-stamp-inactive)))
+ ((and ts
+ (memq last-command '(org-time-stamp org-time-stamp-inactive))
+ (memq this-command '(org-time-stamp org-time-stamp-inactive)))
(insert "--")
- (setq time (let ((this-command this-command))
- (org-read-date arg 'totime nil nil
- default-time default-input inactive)))
(org-insert-time-stamp time (or org-time-was-given arg) inactive))
- ((org-at-timestamp-p t)
- (setq time (let ((this-command this-command))
- (org-read-date arg 'totime nil nil default-time default-input inactive)))
- (when (org-at-timestamp-p t) ; just to get the match data
- ; (setq inactive (eq (char-after (match-beginning 0)) ?\[))
- (replace-match "")
+ (ts
+ ;; Make sure we're on a timestamp. When in the middle of a date
+ ;; range, move arbitrarily to range end.
+ (unless (org-at-timestamp-p t)
+ (skip-chars-forward "-")
+ (org-at-timestamp-p t))
+ (replace-match "")
+ (setq org-last-changed-timestamp
+ (org-insert-time-stamp
+ time (or org-time-was-given arg)
+ inactive nil nil (list org-end-time-was-given)))
+ (when repeater
+ (backward-char)
+ (insert " " repeater)
(setq org-last-changed-timestamp
- (org-insert-time-stamp
- time (or org-time-was-given arg)
- inactive nil nil (list org-end-time-was-given)))
- (when repeater (goto-char (1- (point))) (insert " " repeater)
- (setq org-last-changed-timestamp
- (concat (substring org-last-inserted-timestamp 0 -1)
- " " repeater ">"))))
+ (concat (substring org-last-inserted-timestamp 0 -1)
+ " " repeater ">")))
(message "Timestamp updated"))
- ((equal arg '(16))
- (org-insert-time-stamp (current-time) t inactive))
- (t
- (setq time (let ((this-command this-command))
- (org-read-date arg 'totime nil nil default-time default-input inactive)))
- (org-insert-time-stamp time (or org-time-was-given arg) inactive
- nil nil (list org-end-time-was-given))))))
+ ((equal arg '(16)) (org-insert-time-stamp time t inactive))
+ (t (org-insert-time-stamp
+ time (or org-time-was-given arg) inactive nil nil
+ (list org-end-time-was-given))))))
;; FIXME: can we use this for something else, like computing time differences?
(defun org-get-compact-tod (s)
@@ -16211,7 +16796,7 @@ non-nil."
(if (not t2)
t1
(setq dh (- h2 h1) dm (- m2 m1))
- (if (< dm 0) (setq dm (+ dm 60) dh (1- dh)))
+ (when (< dm 0) (setq dm (+ dm 60) dh (1- dh)))
(concat t1 "+" (number-to-string dh)
(and (/= 0 dm) (format ":%02d" dm)))))))
@@ -16226,7 +16811,7 @@ So these are more for recording a certain time/date."
(defvar org-date-ovl (make-overlay 1 1))
(overlay-put org-date-ovl 'face 'org-date-selected)
-(org-detach-overlay org-date-ovl)
+(delete-overlay org-date-ovl)
(defvar org-ans1) ; dynamically scoped parameter
(defvar org-ans2) ; dynamically scoped parameter
@@ -16243,13 +16828,14 @@ So these are more for recording a certain time/date."
(defvar org-read-date-inactive)
(defvar org-read-date-minibuffer-local-map
- (let* ((org-replace-disputed-keys nil)
- (map (make-sparse-keymap)))
+ (let* ((map (make-sparse-keymap)))
(set-keymap-parent map minibuffer-local-map)
(org-defkey map (kbd ".")
(lambda () (interactive)
;; Are we at the beginning of the prompt?
- (if (looking-back "^[^:]+: ")
+ (if (looking-back "^[^:]+: "
+ (let ((inhibit-field-text-motion t))
+ (line-beginning-position)))
(org-eval-in-calendar '(calendar-goto-today))
(insert "."))))
(org-defkey map (kbd "C-.")
@@ -16316,7 +16902,8 @@ So these are more for recording a certain time/date."
(defvar org-defdecode)
(defvar org-with-time)
-(defun org-read-date (&optional org-with-time to-time from-string prompt
+(defvar calendar-setup) ; Dynamically scoped.
+(defun org-read-date (&optional with-time to-time from-string prompt
default-time default-input inactive)
"Read a date, possibly a time, and make things smooth for the user.
The prompt will suggest to enter an ISO date, but you can also enter anything
@@ -16360,8 +16947,8 @@ If you don't like the calendar, turn it off with
With optional argument TO-TIME, the date will immediately be converted
to an internal time.
-With an optional argument ORG-WITH-TIME, the prompt will suggest to
-also insert a time. Note that when ORG-WITH-TIME is not set, you can
+With an optional argument WITH-TIME, the prompt will suggest to
+also insert a time. Note that when WITH-TIME is not set, you can
still enter a time, and this function will inform the calling routine
about this change. The calling routine may then choose to change the
format used to insert the time stamp into the buffer to include the time.
@@ -16370,75 +16957,90 @@ the user. PROMPT can overwrite the default prompt. DEFAULT-TIME is
the time/date that is used for everything that is not specified by the
user."
(require 'parse-time)
- (let* ((org-time-stamp-rounding-minutes
- (if (equal org-with-time '(16)) '(0 0) org-time-stamp-rounding-minutes))
+ (let* ((org-with-time with-time)
+ (org-time-stamp-rounding-minutes
+ (if (equal org-with-time '(16))
+ '(0 0)
+ org-time-stamp-rounding-minutes))
(org-dcst org-display-custom-times)
(ct (org-current-time))
(org-def (or org-overriding-default-time default-time ct))
(org-defdecode (decode-time org-def))
- (dummy (progn
- (when (< (nth 2 org-defdecode) org-extend-today-until)
- (setcar (nthcdr 2 org-defdecode) -1)
- (setcar (nthcdr 1 org-defdecode) 59)
- (setq org-def (apply 'encode-time org-defdecode)
- org-defdecode (decode-time org-def)))))
- (mouse-autoselect-window nil) ; Don't let the mouse jump
- (calendar-frame-setup nil)
- (calendar-setup nil)
+ (cur-frame (selected-frame))
+ (mouse-autoselect-window nil) ; Don't let the mouse jump
+ (calendar-setup
+ (and (eq calendar-setup 'calendar-only) 'calendar-only))
(calendar-move-hook nil)
(calendar-view-diary-initially-flag nil)
(calendar-view-holidays-initially-flag nil)
- (timestr (format-time-string
- (if org-with-time "%Y-%m-%d %H:%M" "%Y-%m-%d") org-def))
- (prompt (concat (if prompt (concat prompt " ") "")
- (format "Date+time [%s]: " timestr)))
- ans (org-ans0 "") org-ans1 org-ans2 final)
-
- (cond
- (from-string (setq ans from-string))
- (org-read-date-popup-calendar
- (save-excursion
- (save-window-excursion
- (calendar)
- (org-eval-in-calendar '(setq cursor-type nil) t)
- (unwind-protect
- (progn
- (calendar-forward-day (- (time-to-days org-def)
- (calendar-absolute-from-gregorian
- (calendar-current-date))))
- (org-eval-in-calendar nil t)
- (let* ((old-map (current-local-map))
- (map (copy-keymap calendar-mode-map))
- (minibuffer-local-map
- (copy-keymap org-read-date-minibuffer-local-map)))
- (org-defkey map (kbd "RET") 'org-calendar-select)
- (org-defkey map [mouse-1] 'org-calendar-select-mouse)
- (org-defkey map [mouse-2] 'org-calendar-select-mouse)
- (unwind-protect
- (progn
- (use-local-map map)
- (setq org-read-date-inactive inactive)
- (add-hook 'post-command-hook 'org-read-date-display)
- (setq org-ans0 (read-string prompt default-input
- 'org-read-date-history nil))
- ;; org-ans0: from prompt
- ;; org-ans1: from mouse click
- ;; org-ans2: from calendar motion
- (setq ans (concat org-ans0 " " (or org-ans1 org-ans2))))
- (remove-hook 'post-command-hook 'org-read-date-display)
- (use-local-map old-map)
- (when org-read-date-overlay
- (delete-overlay org-read-date-overlay)
- (setq org-read-date-overlay nil)))))
- (bury-buffer "*Calendar*")))))
-
- (t ; Naked prompt only
- (unwind-protect
- (setq ans (read-string prompt default-input
- 'org-read-date-history timestr))
- (when org-read-date-overlay
- (delete-overlay org-read-date-overlay)
- (setq org-read-date-overlay nil)))))
+ ans (org-ans0 "") org-ans1 org-ans2 final cal-frame)
+ ;; Rationalize `org-def' and `org-defdecode', if required.
+ (when (< (nth 2 org-defdecode) org-extend-today-until)
+ (setf (nth 2 org-defdecode) -1)
+ (setf (nth 1 org-defdecode) 59)
+ (setq org-def (apply #'encode-time org-defdecode))
+ (setq org-defdecode (decode-time org-def)))
+ (let* ((timestr (format-time-string
+ (if org-with-time "%Y-%m-%d %H:%M" "%Y-%m-%d")
+ org-def))
+ (prompt (concat (if prompt (concat prompt " ") "")
+ (format "Date+time [%s]: " timestr))))
+ (cond
+ (from-string (setq ans from-string))
+ (org-read-date-popup-calendar
+ (save-excursion
+ (save-window-excursion
+ (calendar)
+ (when (eq calendar-setup 'calendar-only)
+ (setq cal-frame
+ (window-frame (get-buffer-window "*Calendar*" 'visible)))
+ (select-frame cal-frame))
+ (org-eval-in-calendar '(setq cursor-type nil) t)
+ (unwind-protect
+ (progn
+ (calendar-forward-day (- (time-to-days org-def)
+ (calendar-absolute-from-gregorian
+ (calendar-current-date))))
+ (org-eval-in-calendar nil t)
+ (let* ((old-map (current-local-map))
+ (map (copy-keymap calendar-mode-map))
+ (minibuffer-local-map
+ (copy-keymap org-read-date-minibuffer-local-map)))
+ (org-defkey map (kbd "RET") 'org-calendar-select)
+ (org-defkey map [mouse-1] 'org-calendar-select-mouse)
+ (org-defkey map [mouse-2] 'org-calendar-select-mouse)
+ (unwind-protect
+ (progn
+ (use-local-map map)
+ (setq org-read-date-inactive inactive)
+ (add-hook 'post-command-hook 'org-read-date-display)
+ (setq org-ans0
+ (read-string prompt
+ default-input
+ 'org-read-date-history
+ nil))
+ ;; org-ans0: from prompt
+ ;; org-ans1: from mouse click
+ ;; org-ans2: from calendar motion
+ (setq ans
+ (concat org-ans0 " " (or org-ans1 org-ans2))))
+ (remove-hook 'post-command-hook 'org-read-date-display)
+ (use-local-map old-map)
+ (when org-read-date-overlay
+ (delete-overlay org-read-date-overlay)
+ (setq org-read-date-overlay nil)))))
+ (bury-buffer "*Calendar*")
+ (when cal-frame
+ (delete-frame cal-frame)
+ (select-frame-set-input-focus cur-frame))))))
+
+ (t ; Naked prompt only
+ (unwind-protect
+ (setq ans (read-string prompt default-input
+ 'org-read-date-history timestr))
+ (when org-read-date-overlay
+ (delete-overlay org-read-date-overlay)
+ (setq org-read-date-overlay nil))))))
(setq final (org-read-date-analyze ans org-def org-defdecode))
@@ -16499,13 +17101,18 @@ user."
(make-overlay (1- (point-at-eol)) (point-at-eol)))
(org-overlay-display org-read-date-overlay txt 'secondary-selection)))))
-(defun org-read-date-analyze (ans org-def org-defdecode)
+(defun org-read-date-analyze (ans def defdecode)
"Analyze the combined answer of the date prompt."
;; FIXME: cleanup and comment
- (let ((nowdecode (decode-time))
+ ;; Pass `current-time' result to `decode-time' (instead of calling
+ ;; without arguments) so that only `current-time' has to be
+ ;; overridden in tests.
+ (let ((org-def def)
+ (org-defdecode defdecode)
+ (nowdecode (decode-time (current-time)))
delta deltan deltaw deltadef year month day
hour minute second wday pm h2 m2 tl wday1
- iso-year iso-weekday iso-week iso-year iso-date futurep kill-year)
+ iso-year iso-weekday iso-week iso-date futurep kill-year)
(setq org-read-date-analyze-futurep nil
org-read-date-analyze-forced-year nil)
(when (string-match "\\`[ \t]*\\.[ \t]*\\'" ans)
@@ -16521,11 +17128,11 @@ user."
;; info and postpone interpreting it until the rest of the parsing
;; is done.
(when (string-match "\\<\\(?:\\([0-9]+\\)-\\)?[wW]\\([0-9]\\{1,2\\}\\)\\(?:-\\([0-6]\\)\\)?\\([ \t]\\|$\\)" ans)
- (setq iso-year (if (match-end 1)
- (org-small-year-to-year
- (string-to-number (match-string 1 ans))))
- iso-weekday (if (match-end 3)
- (string-to-number (match-string 3 ans)))
+ (setq iso-year (when (match-end 1)
+ (org-small-year-to-year
+ (string-to-number (match-string 1 ans))))
+ iso-weekday (when (match-end 3)
+ (string-to-number (match-string 3 ans)))
iso-week (string-to-number (match-string 2 ans)))
(setq ans (replace-match "" t t ans)))
@@ -16538,7 +17145,7 @@ user."
(string-to-number (format-time-string "%Y"))))
month (string-to-number (match-string 3 ans))
day (string-to-number (match-string 4 ans)))
- (if (< year 100) (setq year (+ 2000 year)))
+ (setq year (org-small-year-to-year year))
(setq ans (replace-match (format "%04d-%02d-%02d\\5" year month day)
t nil ans)))
@@ -16562,26 +17169,26 @@ user."
(string-to-number (format-time-string "%Y"))))
month (string-to-number (match-string 1 ans))
day (string-to-number (match-string 2 ans)))
- (if (< year 100) (setq year (+ 2000 year)))
+ (setq year (org-small-year-to-year year))
(setq ans (replace-match (format "%04d-%02d-%02d\\5" year month day)
t nil ans)))
;; Help matching am/pm times, because `parse-time-string' does not do that.
;; If there is a time with am/pm, and *no* time without it, we convert
;; so that matching will be successful.
- (loop for i from 1 to 2 do ; twice, for end time as well
- (when (and (not (string-match "\\(\\`\\|[^+]\\)[012]?[0-9]:[0-9][0-9]\\([ \t\n]\\|$\\)" ans))
- (string-match "\\([012]?[0-9]\\)\\(:\\([0-5][0-9]\\)\\)?\\(am\\|AM\\|pm\\|PM\\)\\>" ans))
- (setq hour (string-to-number (match-string 1 ans))
- minute (if (match-end 3)
- (string-to-number (match-string 3 ans))
- 0)
- pm (equal ?p
- (string-to-char (downcase (match-string 4 ans)))))
- (if (and (= hour 12) (not pm))
- (setq hour 0)
- (if (and pm (< hour 12)) (setq hour (+ 12 hour))))
- (setq ans (replace-match (format "%02d:%02d" hour minute)
- t t ans))))
+ (cl-loop for i from 1 to 2 do ; twice, for end time as well
+ (when (and (not (string-match "\\(\\`\\|[^+]\\)[012]?[0-9]:[0-9][0-9]\\([ \t\n]\\|$\\)" ans))
+ (string-match "\\([012]?[0-9]\\)\\(:\\([0-5][0-9]\\)\\)?\\(am\\|AM\\|pm\\|PM\\)\\>" ans))
+ (setq hour (string-to-number (match-string 1 ans))
+ minute (if (match-end 3)
+ (string-to-number (match-string 3 ans))
+ 0)
+ pm (equal ?p
+ (string-to-char (downcase (match-string 4 ans)))))
+ (if (and (= hour 12) (not pm))
+ (setq hour 0)
+ (when (and pm (< hour 12)) (setq hour (+ 12 hour))))
+ (setq ans (replace-match (format "%02d:%02d" hour minute)
+ t t ans))))
;; Check if a time range is given as a duration
(when (string-match "\\([012]?[0-9]\\):\\([0-6][0-9]\\)\\+\\([012]?[0-9]\\)\\(:\\([0-5][0-9]\\)\\)?" ans)
@@ -16590,7 +17197,7 @@ user."
minute (string-to-number (match-string 2 ans))
m2 (+ minute (if (match-end 5) (string-to-number
(match-string 5 ans))0)))
- (if (>= m2 60) (setq h2 (1+ h2) m2 (- m2 60)))
+ (when (>= m2 60) (setq h2 (1+ h2) m2 (- m2 60)))
(setq ans (replace-match (format "%02d:%02d-%02d:%02d" hour minute h2 m2)
t t ans)))
@@ -16605,16 +17212,35 @@ user."
(setq tl (parse-time-string ans)
day (or (nth 3 tl) (nth 3 org-defdecode))
- month (or (nth 4 tl)
- (if (and org-read-date-prefer-future
- (nth 3 tl) (< (nth 3 tl) (nth 3 nowdecode)))
- (prog1 (1+ (nth 4 nowdecode)) (setq futurep t))
- (nth 4 org-defdecode)))
- year (or (and (not kill-year) (nth 5 tl))
- (if (and org-read-date-prefer-future
- (nth 4 tl) (< (nth 4 tl) (nth 4 nowdecode)))
- (prog1 (1+ (nth 5 nowdecode)) (setq futurep t))
- (nth 5 org-defdecode)))
+ month
+ (cond ((nth 4 tl))
+ ((not org-read-date-prefer-future) (nth 4 org-defdecode))
+ ;; Day was specified. Make sure DAY+MONTH
+ ;; combination happens in the future.
+ ((nth 3 tl)
+ (setq futurep t)
+ (if (< day (nth 3 nowdecode)) (1+ (nth 4 nowdecode))
+ (nth 4 nowdecode)))
+ (t (nth 4 org-defdecode)))
+ year
+ (cond ((and (not kill-year) (nth 5 tl)))
+ ((not org-read-date-prefer-future) (nth 5 org-defdecode))
+ ;; Month was guessed in the future and is at least
+ ;; equal to NOWDECODE's. Fix year accordingly.
+ (futurep
+ (if (or (> month (nth 4 nowdecode))
+ (>= day (nth 3 nowdecode)))
+ (nth 5 nowdecode)
+ (1+ (nth 5 nowdecode))))
+ ;; Month was specified. Make sure MONTH+YEAR
+ ;; combination happens in the future.
+ ((nth 4 tl)
+ (setq futurep t)
+ (cond ((> month (nth 4 nowdecode)) (nth 5 nowdecode))
+ ((< month (nth 4 nowdecode)) (1+ (nth 5 nowdecode)))
+ ((< day (nth 3 nowdecode)) (1+ (nth 5 nowdecode)))
+ (t (nth 5 nowdecode))))
+ (t (nth 5 org-defdecode)))
hour (or (nth 2 tl) (nth 2 org-defdecode))
minute (or (nth 1 tl) (nth 1 org-defdecode))
second (or (nth 0 tl) 0)
@@ -16643,7 +17269,7 @@ user."
day (or iso-weekday wday 1)
wday nil ; to make sure that the trigger below does not match
iso-date (calendar-gregorian-from-absolute
- (calendar-absolute-from-iso
+ (calendar-iso-to-absolute
(list iso-week day year))))
; FIXME: Should we also push ISO weeks into the future?
; (when (and org-read-date-prefer-future
@@ -16652,7 +17278,7 @@ user."
; (time-to-days (current-time))))
; (setq year (1+ year)
; iso-date (calendar-gregorian-from-absolute
- ; (calendar-absolute-from-iso
+ ; (calendar-iso-to-absolute
; (list iso-week day year)))))
(setq month (car iso-date)
year (nth 2 iso-date)
@@ -16660,7 +17286,10 @@ user."
(deltan
(setq futurep nil)
(unless deltadef
- (let ((now (decode-time)))
+ ;; Pass `current-time' result to `decode-time' (instead of
+ ;; calling without arguments) so that only `current-time' has
+ ;; to be overridden in tests.
+ (let ((now (decode-time (current-time))))
(setq day (nth 3 now) month (nth 4 now) year (nth 5 now))))
(cond ((member deltaw '("d" "")) (setq day (+ day deltan)))
((equal deltaw "w") (setq day (+ day (* 7 deltan))))
@@ -16672,17 +17301,17 @@ user."
(setq wday1 (nth 6 (decode-time (encode-time 0 0 0 day month year))))
(unless (equal wday wday1)
(setq day (+ day (% (- wday wday1 -7) 7))))))
- (if (and (boundp 'org-time-was-given)
- (nth 2 tl))
- (setq org-time-was-given t))
- (if (< year 100) (setq year (+ 2000 year)))
+ (when (and (boundp 'org-time-was-given)
+ (nth 2 tl))
+ (setq org-time-was-given t))
+ (when (< year 100) (setq year (+ 2000 year)))
;; Check of the date is representable
(if org-read-date-force-compatible-dates
(progn
- (if (< year 1970)
- (setq year 1970 org-read-date-analyze-forced-year t))
- (if (> year 2037)
- (setq year 2037 org-read-date-analyze-forced-year t)))
+ (when (< year 1970)
+ (setq year 1970 org-read-date-analyze-forced-year t))
+ (when (> year 2037)
+ (setq year 2037 org-read-date-analyze-forced-year t)))
(condition-case nil
(ignore (encode-time second minute hour day month year))
(error
@@ -16722,12 +17351,11 @@ DEF-FLAG is t when a double ++ or -- indicates shift relative to
(if wday1
(progn
(setq delta (mod (+ 7 (- wday1 wday)) 7))
- (if (= delta 0) (setq delta 7))
- (if (= dir ?-)
- (progn
- (setq delta (- delta 7))
- (if (= delta 0) (setq delta -7))))
- (if (> n 1) (setq delta (+ delta (* (1- n) (if (= dir ?-) -7 7)))))
+ (when (= delta 0) (setq delta 7))
+ (when (= dir ?-)
+ (setq delta (- delta 7))
+ (when (= delta 0) (setq delta -7)))
+ (when (> n 1) (setq delta (+ delta (* (1- n) (if (= dir ?-) -7 7)))))
(list delta "d" rel))
(list (* n (if (= dir ?-) -1 1)) what rel)))))
@@ -16736,23 +17364,14 @@ DEF-FLAG is t when a double ++ or -- indicates shift relative to
The internal representation needed by the calendar is (month day year).
This is a wrapper to handle the brain-dead convention in calendar that
user function argument order change dependent on argument order."
- (if (boundp 'calendar-date-style)
- (cond
- ((eq calendar-date-style 'american)
- (list arg1 arg2 arg3))
- ((eq calendar-date-style 'european)
- (list arg2 arg1 arg3))
- ((eq calendar-date-style 'iso)
- (list arg2 arg3 arg1)))
- (org-no-warnings ;; european-calendar-style is obsolete as of version 23.1
- (if (org-bound-and-true-p european-calendar-style)
- (list arg2 arg1 arg3)
- (list arg1 arg2 arg3)))))
+ (pcase calendar-date-style
+ (`american (list arg1 arg2 arg3))
+ (`european (list arg2 arg1 arg3))
+ (`iso (list arg2 arg3 arg1))))
(defun org-eval-in-calendar (form &optional keepdate)
"Eval FORM in the calendar window and return to current window.
-When KEEPDATE is non-nil, update `org-ans2' from the cursor date,
-otherwise stick to the current value of `org-ans2'."
+Unless KEEPDATE is non-nil, update `org-ans2' to the cursor date."
(let ((sf (selected-frame))
(sw (selected-window)))
(select-window (get-buffer-window "*Calendar*" t))
@@ -16763,7 +17382,7 @@ otherwise stick to the current value of `org-ans2'."
(setq org-ans2 (format-time-string "%Y-%m-%d" time))))
(move-overlay org-date-ovl (1- (point)) (1+ (point)) (current-buffer))
(select-window sw)
- (org-select-frame-set-input-focus sf)))
+ (select-frame-set-input-focus sf)))
(defun org-calendar-select ()
"Return to `org-read-date' with the date currently selected.
@@ -16773,10 +17392,11 @@ This is used by `org-read-date' in a temporary keymap for the calendar buffer."
(let* ((date (calendar-cursor-to-date))
(time (encode-time 0 0 0 (nth 1 date) (nth 0 date) (nth 2 date))))
(setq org-ans1 (format-time-string "%Y-%m-%d" time)))
- (if (active-minibuffer-window) (exit-minibuffer))))
+ (when (active-minibuffer-window) (exit-minibuffer))))
(defun org-insert-time-stamp (time &optional with-hm inactive pre post extra)
"Insert a date stamp for the date given by the internal TIME.
+See `format-time-string' for the format of TIME.
WITH-HM means use the stamp format that includes the time of the day.
INACTIVE means use square brackets instead of angular ones, so that the
stamp will not contribute to the agenda.
@@ -16785,7 +17405,7 @@ stamp.
The command returns the inserted time stamp."
(let ((fmt (funcall (if with-hm 'cdr 'car) org-time-stamp-formats))
stamp)
- (if inactive (setq fmt (concat "[" (substring fmt 1 -1) "]")))
+ (when inactive (setq fmt (concat "[" (substring fmt 1 -1) "]")))
(insert-before-markers (or pre ""))
(when (listp extra)
(setq extra (car extra))
@@ -16808,14 +17428,12 @@ The command returns the inserted time stamp."
(unless org-display-custom-times
(let ((p (point-min)) (bmp (buffer-modified-p)))
(while (setq p (next-single-property-change p 'display))
- (if (and (get-text-property p 'display)
- (eq (get-text-property p 'face) 'org-date))
- (remove-text-properties
- p (setq p (next-single-property-change p 'display))
- '(display t))))
+ (when (and (get-text-property p 'display)
+ (eq (get-text-property p 'face) 'org-date))
+ (remove-text-properties
+ p (setq p (next-single-property-change p 'display))
+ '(display t))))
(set-buffer-modified-p bmp)))
- (if (featurep 'xemacs)
- (remove-text-properties (point-min) (point-max) '(end-glyph t)))
(org-restart-font-lock)
(setq org-table-may-need-update t)
(if org-display-custom-times
@@ -16828,8 +17446,8 @@ The command returns the inserted time stamp."
t1 w1 with-hm tf time str w2 (off 0))
(save-match-data
(setq t1 (org-parse-time-string ts t))
- (if (string-match "\\(-[0-9]+:[0-9]+\\)?\\( [.+]?\\+[0-9]+[hdwmy]\\(/[0-9]+[hdwmy]\\)?\\)?\\'" ts)
- (setq off (- (match-end 0) (match-beginning 0)))))
+ (when (string-match "\\(-[0-9]+:[0-9]+\\)?\\( [.+]?\\+[0-9]+[hdwmy]\\(/[0-9]+[hdwmy]\\)?\\)?\\'" ts)
+ (setq off (- (match-end 0) (match-beginning 0)))))
(setq end (- end off))
(setq w1 (- end beg)
with-hm (and (nth 1 t1) (nth 2 t1))
@@ -16840,41 +17458,10 @@ The command returns the inserted time stamp."
(substring tf 1 -1) (apply 'encode-time time))
nil 'mouse-face 'highlight)
w2 (length str))
- (if (not (= w2 w1))
- (add-text-properties (1+ beg) (+ 2 beg)
- (list 'org-dwidth t 'org-dwidth-n (- w1 w2))))
- (if (featurep 'xemacs)
- (progn
- (put-text-property beg end 'invisible t)
- (put-text-property beg end 'end-glyph (make-glyph str)))
- (put-text-property beg end 'display str))))
-
-(defun org-translate-time (string)
- "Translate all timestamps in STRING to custom format.
-But do this only if the variable `org-display-custom-times' is set."
- (when org-display-custom-times
- (save-match-data
- (let* ((start 0)
- (re org-ts-regexp-both)
- t1 with-hm inactive tf time str beg end)
- (while (setq start (string-match re string start))
- (setq beg (match-beginning 0)
- end (match-end 0)
- t1 (save-match-data
- (org-parse-time-string (substring string beg end) t))
- with-hm (and (nth 1 t1) (nth 2 t1))
- inactive (equal (substring string beg (1+ beg)) "[")
- tf (funcall (if with-hm 'cdr 'car)
- org-time-stamp-custom-formats)
- time (org-fix-decoded-time t1)
- str (format-time-string
- (concat
- (if inactive "[" "<") (substring tf 1 -1)
- (if inactive "]" ">"))
- (apply 'encode-time time))
- string (replace-match str t t string)
- start (+ start (length str)))))))
- string)
+ (unless (= w2 w1)
+ (add-text-properties (1+ beg) (+ 2 beg)
+ (list 'org-dwidth t 'org-dwidth-n (- w1 w2))))
+ (put-text-property beg end 'display str)))
(defun org-fix-decoded-time (time)
"Set 0 instead of nil for the first 6 elements of time.
@@ -16882,19 +17469,17 @@ Don't touch the rest."
(let ((n 0))
(mapcar (lambda (x) (if (< (setq n (1+ n)) 7) (or x 0) x)) time)))
-(define-obsolete-function-alias 'org-days-to-time 'org-time-stamp-to-now "24.4")
-
(defun org-time-stamp-to-now (timestamp-string &optional seconds)
"Difference between TIMESTAMP-STRING and now in days.
If SECONDS is non-nil, return the difference in seconds."
- (let ((fdiff (if seconds 'float-time 'time-to-days)))
+ (let ((fdiff (if seconds #'float-time #'time-to-days)))
(- (funcall fdiff (org-time-string-to-time timestamp-string))
(funcall fdiff (current-time)))))
-(defun org-deadline-close (timestamp-string &optional ndays)
+(defun org-deadline-close-p (timestamp-string &optional ndays)
"Is the time in TIMESTAMP-STRING close to the current date?"
(setq ndays (or ndays (org-get-wdays timestamp-string)))
- (and (< (org-time-stamp-to-now timestamp-string) ndays)
+ (and (<= (org-time-stamp-to-now timestamp-string) ndays)
(not (org-entry-is-done-p))))
(defun org-get-wdays (ts &optional delay zero-delay)
@@ -16930,14 +17515,15 @@ This is used by `org-read-date' in a temporary keymap for the calendar buffer."
(let* ((date (calendar-cursor-to-date))
(time (encode-time 0 0 0 (nth 1 date) (nth 0 date) (nth 2 date))))
(setq org-ans1 (format-time-string "%Y-%m-%d" time)))
- (if (active-minibuffer-window) (exit-minibuffer))))
+ (when (active-minibuffer-window) (exit-minibuffer))))
(defun org-check-deadlines (ndays)
"Check if there are any deadlines due or past due.
A deadline is considered due if it happens within `org-deadline-warning-days'
days from today's date. If the deadline appears in an entry marked DONE,
-it is not shown. The prefix arg NDAYS can be used to test that many
-days. If the prefix is a raw \\[universal-argument] prefix, all deadlines are shown."
+it is not shown. A numeric prefix argument NDAYS can be used to test that
+many days. If the prefix is a raw `\\[universal-argument]', all deadlines \
+are shown."
(interactive "P")
(let* ((org-warn-days
(cond
@@ -16947,8 +17533,7 @@ days. If the prefix is a raw \\[universal-argument] prefix, all deadlines are s
(case-fold-search nil)
(regexp (concat "\\<" org-deadline-string " *<\\([^>]+\\)>"))
(callback
- (lambda () (org-deadline-close (match-string 1) org-warn-days))))
-
+ (lambda () (org-deadline-close-p (match-string 1) org-warn-days))))
(message "%d deadlines past-due or due within %d days"
(org-occur regexp nil callback)
org-warn-days)))
@@ -16966,39 +17551,61 @@ Allowed values for TYPE are:
When TYPE is nil, fall back on returning a regexp that matches
both scheduled and deadline timestamps."
- (cond ((eq type 'all) "\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\(?: +[^]+0-9>\r\n -]+\\)?\\(?: +[0-9]\\{1,2\\}:[0-9]\\{2\\}\\)?\\)")
- ((eq type 'active) org-ts-regexp)
- ((eq type 'inactive) "\\[\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} ?[^\r\n>]*?\\)\\]")
- ((eq type 'scheduled) (concat "\\<" org-scheduled-string " *<\\([^>]+\\)>"))
- ((eq type 'deadline) (concat "\\<" org-deadline-string " *<\\([^>]+\\)>"))
- ((eq type 'closed) (concat org-closed-string " \\[\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} ?[^\r\n>]*?\\)\\]"))
- ((eq type 'scheduled-or-deadline)
- (concat "\\<\\(?:" org-deadline-string "\\|" org-scheduled-string "\\) *<\\([^>]+\\)>"))))
-
-(defun org-check-before-date (date)
- "Check if there are deadlines or scheduled entries before DATE."
+ (cl-case type
+ (all org-ts-regexp-both)
+ (active org-ts-regexp)
+ (inactive org-ts-regexp-inactive)
+ (scheduled org-scheduled-time-regexp)
+ (deadline org-deadline-time-regexp)
+ (closed org-closed-time-regexp)
+ (otherwise
+ (concat "\\<"
+ (regexp-opt (list org-deadline-string org-scheduled-string))
+ " *<\\([^>]+\\)>"))))
+
+(defun org-check-before-date (d)
+ "Check if there are deadlines or scheduled entries before date D."
(interactive (list (org-read-date)))
- (let ((case-fold-search nil)
- (regexp (org-re-timestamp org-ts-type))
- (callback
- (lambda () (time-less-p
- (org-time-string-to-time (match-string 1))
- (org-time-string-to-time date)))))
+ (let* ((case-fold-search nil)
+ (regexp (org-re-timestamp org-ts-type))
+ (ts-type org-ts-type)
+ (callback
+ (lambda ()
+ (let ((match (match-string 1)))
+ (and (if (memq ts-type '(active inactive all))
+ (eq (org-element-type (save-excursion
+ (backward-char)
+ (org-element-context)))
+ 'timestamp)
+ (org-at-planning-p))
+ (time-less-p
+ (org-time-string-to-time match)
+ (org-time-string-to-time d)))))))
(message "%d entries before %s"
- (org-occur regexp nil callback) date)))
+ (org-occur regexp nil callback)
+ d)))
-(defun org-check-after-date (date)
- "Check if there are deadlines or scheduled entries after DATE."
+(defun org-check-after-date (d)
+ "Check if there are deadlines or scheduled entries after date D."
(interactive (list (org-read-date)))
- (let ((case-fold-search nil)
- (regexp (org-re-timestamp org-ts-type))
- (callback
- (lambda () (not
- (time-less-p
- (org-time-string-to-time (match-string 1))
- (org-time-string-to-time date))))))
+ (let* ((case-fold-search nil)
+ (regexp (org-re-timestamp org-ts-type))
+ (ts-type org-ts-type)
+ (callback
+ (lambda ()
+ (let ((match (match-string 1)))
+ (and (if (memq ts-type '(active inactive all))
+ (eq (org-element-type (save-excursion
+ (backward-char)
+ (org-element-context)))
+ 'timestamp)
+ (org-at-planning-p))
+ (not (time-less-p
+ (org-time-string-to-time match)
+ (org-time-string-to-time d))))))))
(message "%d entries after %s"
- (org-occur regexp nil callback) date)))
+ (org-occur regexp nil callback)
+ d)))
(defun org-check-dates-range (start-date end-date)
"Check for deadlines/scheduled entries between START-DATE and END-DATE."
@@ -17007,15 +17614,22 @@ both scheduled and deadline timestamps."
(let ((case-fold-search nil)
(regexp (org-re-timestamp org-ts-type))
(callback
- (lambda ()
- (let ((match (match-string 1)))
- (and
- (not (time-less-p
- (org-time-string-to-time match)
- (org-time-string-to-time start-date)))
- (time-less-p
- (org-time-string-to-time match)
- (org-time-string-to-time end-date)))))))
+ (let ((type org-ts-type))
+ (lambda ()
+ (let ((match (match-string 1)))
+ (and
+ (if (memq type '(active inactive all))
+ (eq (org-element-type (save-excursion
+ (backward-char)
+ (org-element-context)))
+ 'timestamp)
+ (org-at-planning-p))
+ (not (time-less-p
+ (org-time-string-to-time match)
+ (org-time-string-to-time start-date)))
+ (time-less-p
+ (org-time-string-to-time match)
+ (org-time-string-to-time end-date))))))))
(message "%d entries between %s and %s"
(org-occur regexp nil callback) start-date end-date)))
@@ -17034,8 +17648,8 @@ days in order to avoid rounding problems."
(unless (org-at-date-range-p t)
(goto-char (point-at-bol))
(re-search-forward org-tr-regexp-both (point-at-eol) t))
- (if (not (org-at-date-range-p t))
- (user-error "Not at a time-stamp range, and none found in current line")))
+ (unless (org-at-date-range-p t)
+ (user-error "Not at a time-stamp range, and none found in current line")))
(let* ((ts1 (match-string 1))
(ts2 (match-string 2))
(havetime (or (> (length ts1) 15) (> (length ts2) 15)))
@@ -17073,27 +17687,31 @@ days in order to avoid rounding problems."
(setq align t)
(and (looking-at " *|") (goto-char (match-end 0))))
(goto-char match-end))
- (if (looking-at
- "\\( *-? *[0-9]+y\\)?\\( *[0-9]+d\\)? *[0-9][0-9]:[0-9][0-9]")
- (replace-match ""))
- (if negative (insert " -"))
+ (when (looking-at
+ "\\( *-? *[0-9]+y\\)?\\( *[0-9]+d\\)? *[0-9][0-9]:[0-9][0-9]")
+ (replace-match ""))
+ (when negative (insert " -"))
(if (> y 0) (insert " " (format (if havetime fy fy1) y d h m))
(if (> d 0) (insert " " (format (if havetime fd fd1) d h m))
(insert " " (format fh h m))))
- (if align (org-table-align))
+ (when align (org-table-align))
(message "Time difference inserted")))))
(defun org-make-tdiff-string (y d h m)
(let ((fmt "")
(l nil))
- (if (> y 0) (setq fmt (concat fmt "%d year" (if (> y 1) "s" "") " ")
- l (push y l)))
- (if (> d 0) (setq fmt (concat fmt "%d day" (if (> d 1) "s" "") " ")
- l (push d l)))
- (if (> h 0) (setq fmt (concat fmt "%d hour" (if (> h 1) "s" "") " ")
- l (push h l)))
- (if (> m 0) (setq fmt (concat fmt "%d minute" (if (> m 1) "s" "") " ")
- l (push m l)))
+ (when (> y 0)
+ (setq fmt (concat fmt "%d year" (if (> y 1) "s" "") " "))
+ (push y l))
+ (when (> d 0)
+ (setq fmt (concat fmt "%d day" (if (> d 1) "s" "") " "))
+ (push d l))
+ (when (> h 0)
+ (setq fmt (concat fmt "%d hour" (if (> h 1) "s" "") " "))
+ (push h l))
+ (when (> m 0)
+ (setq fmt (concat fmt "%d minute" (if (> m 1) "s" "") " "))
+ (push m l))
(apply 'format fmt (nreverse l))))
(defun org-time-string-to-time (s &optional buffer pos)
@@ -17110,28 +17728,40 @@ days in order to avoid rounding problems."
"Convert a timestamp string to a number of seconds."
(float-time (org-time-string-to-time s)))
-(defun org-time-string-to-absolute (s &optional daynr prefer show-all buffer pos)
- "Convert a time stamp to an absolute day number.
-If there is a specifier for a cyclic time stamp, get the closest
-date to DAYNR.
-PREFER and SHOW-ALL are passed through to `org-closest-date'.
-The variable `date' is bound by the calendar when this is called."
+(org-define-error 'org-diary-sexp-no-match "Unable to match diary sexp")
+
+(defun org-time-string-to-absolute (s &optional daynr prefer buffer pos)
+ "Convert time stamp S to an absolute day number.
+
+If DAYNR in non-nil, and there is a specifier for a cyclic time
+stamp, get the closest date to DAYNR. If PREFER is
+`past' (respectively `future') return a date past (respectively
+after) or equal to DAYNR.
+
+POS is the location of time stamp S, as a buffer position in
+BUFFER.
+
+Diary sexp timestamps are matched against DAYNR, when non-nil.
+If matching fails or DAYNR is nil, `org-diary-sexp-no-match' is
+signaled."
(cond
- ((and daynr (string-match "\\`%%\\((.*)\\)" s))
- (if (org-diary-sexp-entry (match-string 1 s) "" date)
+ ((string-match "\\`%%\\((.*)\\)" s)
+ ;; Sexp timestamp: try to match DAYNR, if available, since we're
+ ;; only able to match individual dates. If it fails, raise an
+ ;; error.
+ (if (and daynr
+ (org-diary-sexp-entry
+ (match-string 1 s) "" (calendar-gregorian-from-absolute daynr)))
daynr
- (+ daynr 1000)))
- ((and daynr (string-match "\\+[0-9]+[hdwmy]" s))
- (org-closest-date s (if (and (boundp 'daynr) (integerp daynr)) daynr
- (time-to-days (current-time))) (match-string 0 s)
- prefer show-all))
+ (signal 'org-diary-sexp-no-match (list s))))
+ (daynr (org-closest-date s daynr prefer))
(t (time-to-days
(condition-case errdata
- (apply 'encode-time (org-parse-time-string s))
+ (apply #'encode-time (org-parse-time-string s))
(error (error "Bad timestamp `%s'%s\nError was: %s"
- s (if (not (and buffer pos))
- ""
- (format-message " at %d in buffer `%s'" pos buffer))
+ s
+ (if (not (and buffer pos)) ""
+ (format-message " at %d in buffer `%s'" pos buffer))
(cdr errdata))))))))
(defun org-days-to-iso-week (days)
@@ -17141,43 +17771,46 @@ The variable `date' is bound by the calendar when this is called."
(defun org-small-year-to-year (year)
"Convert 2-digit years into 4-digit years.
-38-99 are mapped into 1938-1999. 1-37 are mapped into 2001-2037.
-The year 2000 cannot be abbreviated. Any year larger than 99
-is returned unchanged."
- (if (< year 38)
- (setq year (+ 2000 year))
- (if (< year 100)
- (setq year (+ 1900 year))))
- year)
+YEAR is expanded into one of the 30 next years, if possible, or
+into a past one. Any year larger than 99 is returned unchanged."
+ (if (>= year 100) year
+ (let* ((current (string-to-number (format-time-string "%Y" (current-time))))
+ (century (/ current 100))
+ (offset (- year (% current 100))))
+ (cond ((> offset 30) (+ (* (1- century) 100) year))
+ ((> offset -70) (+ (* century 100) year))
+ (t (+ (* (1+ century) 100) year))))))
(defun org-time-from-absolute (d)
"Return the time corresponding to date D.
D may be an absolute day number, or a calendar-type list (month day year)."
- (if (numberp d) (setq d (calendar-gregorian-from-absolute d)))
+ (when (numberp d) (setq d (calendar-gregorian-from-absolute d)))
(encode-time 0 0 0 (nth 1 d) (car d) (nth 2 d)))
+(defvar org-agenda-current-date)
(defun org-calendar-holiday ()
- "List of holidays, for Diary display in Org-mode."
+ "List of holidays, for Diary display in Org mode."
(require 'holidays)
- (let ((hl (funcall
- (if (fboundp 'calendar-check-holidays)
- 'calendar-check-holidays 'check-calendar-holidays) date)))
- (if hl (mapconcat 'identity hl "; "))))
+ (let ((hl (calendar-check-holidays org-agenda-current-date)))
+ (and hl (mapconcat #'identity hl "; "))))
-(defun org-diary-sexp-entry (sexp entry date)
- "Process a SEXP diary ENTRY for DATE."
+(defun org-diary-sexp-entry (sexp entry d)
+ "Process a SEXP diary ENTRY for date D."
(require 'diary-lib)
- (let ((result (if calendar-debug-sexp
- (let ((stack-trace-on-error t))
- (eval (car (read-from-string sexp))))
- (condition-case nil
- (eval (car (read-from-string sexp)))
- (error
- (beep)
- (message "Bad sexp at line %d in %s: %s"
- (org-current-line)
- (buffer-file-name) sexp)
- (sleep-for 2))))))
+ ;; `org-anniversary' and alike expect ENTRY and DATE to be bound
+ ;; dynamically.
+ (let* ((sexp `(let ((entry ,entry)
+ (date ',d))
+ ,(car (read-from-string sexp))))
+ (result (if calendar-debug-sexp (eval sexp)
+ (condition-case nil
+ (eval sexp)
+ (error
+ (beep)
+ (message "Bad sexp at line %d in %s: %s"
+ (org-current-line)
+ (buffer-file-name) sexp)
+ (sleep-for 2))))))
(cond ((stringp result) (split-string result "; "))
((and (consp result)
(not (consp (cdr result)))
@@ -17189,9 +17822,7 @@ D may be an absolute day number, or a calendar-type list (month day year)."
(defun org-diary-to-ical-string (frombuf)
"Get iCalendar entries from diary entries in buffer FROMBUF.
This uses the icalendar.el library."
- (let* ((tmpdir (if (featurep 'xemacs)
- (temp-directory)
- temporary-file-directory))
+ (let* ((tmpdir temporary-file-directory)
(tmpfile (make-temp-name
(expand-file-name "orgics" tmpdir)))
buf rtn b e)
@@ -17200,125 +17831,146 @@ This uses the icalendar.el library."
(setq buf (find-buffer-visiting tmpfile))
(set-buffer buf)
(goto-char (point-min))
- (if (re-search-forward "^BEGIN:VEVENT" nil t)
- (setq b (match-beginning 0)))
+ (when (re-search-forward "^BEGIN:VEVENT" nil t)
+ (setq b (match-beginning 0)))
(goto-char (point-max))
- (if (re-search-backward "^END:VEVENT" nil t)
- (setq e (match-end 0)))
+ (when (re-search-backward "^END:VEVENT" nil t)
+ (setq e (match-end 0)))
(setq rtn (if (and b e) (concat (buffer-substring b e) "\n") "")))
(kill-buffer buf)
(delete-file tmpfile)
rtn))
-(defun org-closest-date (start current change prefer show-all)
- "Find the date closest to CURRENT that is consistent with START and CHANGE.
-When PREFER is `past', return a date that is either CURRENT or past.
-When PREFER is `future', return a date that is either CURRENT or future.
-When SHOW-ALL is nil, only return the current occurrence of a time stamp."
- ;; Make the proper lists from the dates
- (catch 'exit
- (let ((a1 '(("h" . hour)
- ("d" . day)
- ("w" . week)
- ("m" . month)
- ("y" . year)))
- (shour (nth 2 (org-parse-time-string start)))
- dn dw sday cday n1 n2 n0
- d m y y1 y2 date1 date2 nmonths nm ny m2)
-
- (setq start (org-date-to-gregorian start)
- current (org-date-to-gregorian
- (if show-all
- current
- (time-to-days (current-time))))
- sday (calendar-absolute-from-gregorian start)
- cday (calendar-absolute-from-gregorian current))
-
- (if (<= cday sday) (throw 'exit sday))
-
- (if (string-match "\\(\\+[0-9]+\\)\\([hdwmy]\\)" change)
- (setq dn (string-to-number (match-string 1 change))
- dw (cdr (assoc (match-string 2 change) a1)))
- (user-error "Invalid change specifier: %s" change))
- (if (eq dw 'week) (setq dw 'day dn (* 7 dn)))
- (cond
- ((eq dw 'hour)
- (let ((missing-hours
- (mod (+ (- (* 24 (- cday sday)) shour) org-extend-today-until)
- dn)))
- (setq n1 (if (zerop missing-hours) cday
- (- cday (1+ (floor (/ missing-hours 24)))))
- n2 (+ cday (floor (/ (- dn missing-hours) 24))))))
- ((eq dw 'day)
- (setq n1 (+ sday (* dn (floor (/ (- cday sday) dn))))
- n2 (+ n1 dn)))
- ((eq dw 'year)
- (setq d (nth 1 start) m (car start) y1 (nth 2 start) y2 (nth 2 current))
- (setq y1 (+ (* (floor (/ (- y2 y1) dn)) dn) y1))
- (setq date1 (list m d y1)
- n1 (calendar-absolute-from-gregorian date1)
- date2 (list m d (+ y1 (* (if (< n1 cday) 1 -1) dn)))
- n2 (calendar-absolute-from-gregorian date2)))
- ((eq dw 'month)
- ;; approx number of month between the two dates
- (setq nmonths (floor (/ (- cday sday) 30.436875)))
- ;; How often does dn fit in there?
- (setq d (nth 1 start) m (car start) y (nth 2 start)
- nm (* dn (max 0 (1- (floor (/ nmonths dn)))))
- m (+ m nm)
- ny (floor (/ m 12))
- y (+ y ny)
- m (- m (* ny 12)))
- (while (> m 12) (setq m (- m 12) y (1+ y)))
- (setq n1 (calendar-absolute-from-gregorian (list m d y)))
- (setq m2 (+ m dn) y2 y)
- (if (> m2 12) (setq y2 (1+ y2) m2 (- m2 12)))
- (setq n2 (calendar-absolute-from-gregorian (list m2 d y2)))
- (while (<= n2 cday)
- (setq n1 n2 m m2 y y2)
- (setq m2 (+ m dn) y2 y)
- (if (> m2 12) (setq y2 (1+ y2) m2 (- m2 12)))
- (setq n2 (calendar-absolute-from-gregorian (list m2 d y2))))))
- ;; Make sure n1 is the earlier date
- (setq n0 n1 n1 (min n1 n2) n2 (max n0 n2))
- (if show-all
- (cond
- ((eq prefer 'past) (if (= cday n2) n2 n1))
- ((eq prefer 'future) (if (= cday n1) n1 n2))
- (t (if (> (abs (- cday n1)) (abs (- cday n2))) n2 n1)))
- (cond
- ((eq prefer 'past) (if (= cday n2) n2 n1))
- ((eq prefer 'future) (if (= cday n1) n1 n2))
- (t (if (= cday n1) n1 n2)))))))
-
-(defun org-date-to-gregorian (date)
- "Turn any specification of DATE into a Gregorian date for the calendar."
- (cond ((integerp date) (calendar-gregorian-from-absolute date))
- ((and (listp date) (= (length date) 3)) date)
- ((stringp date)
- (setq date (org-parse-time-string date))
- (list (nth 4 date) (nth 3 date) (nth 5 date)))
- ((listp date)
- (list (nth 4 date) (nth 3 date) (nth 5 date)))))
-
-(defun org-parse-time-string (s &optional nodefault)
- "Parse the standard Org-mode time string.
+(defun org-closest-date (start current prefer)
+ "Return closest date to CURRENT starting from START.
+
+CURRENT and START are both time stamps.
+
+When PREFER is `past', return a date that is either CURRENT or
+past. When PREFER is `future', return a date that is either
+CURRENT or future.
+
+Only time stamps with a repeater are modified. Any other time
+stamp stay unchanged. In any case, return value is an absolute
+day number."
+ (if (not (string-match "\\+\\([0-9]+\\)\\([hdwmy]\\)" start))
+ ;; No repeater. Do not shift time stamp.
+ (time-to-days (apply #'encode-time (org-parse-time-string start)))
+ (let ((value (string-to-number (match-string 1 start)))
+ (type (match-string 2 start)))
+ (if (= 0 value)
+ ;; Repeater with a 0-value is considered as void.
+ (time-to-days (apply #'encode-time (org-parse-time-string start)))
+ (let* ((base (org-date-to-gregorian start))
+ (target (org-date-to-gregorian current))
+ (sday (calendar-absolute-from-gregorian base))
+ (cday (calendar-absolute-from-gregorian target))
+ n1 n2)
+ ;; If START is already past CURRENT, just return START.
+ (if (<= cday sday) sday
+ ;; Compute closest date before (N1) and closest date past
+ ;; (N2) CURRENT.
+ (pcase type
+ ("h"
+ (let ((missing-hours
+ (mod (+ (- (* 24 (- cday sday))
+ (nth 2 (org-parse-time-string start)))
+ org-extend-today-until)
+ value)))
+ (setf n1 (if (= missing-hours 0) cday
+ (- cday (1+ (/ missing-hours 24)))))
+ (setf n2 (+ cday (/ (- value missing-hours) 24)))))
+ ((or "d" "w")
+ (let ((value (if (equal type "w") (* 7 value) value)))
+ (setf n1 (+ sday (* value (/ (- cday sday) value))))
+ (setf n2 (+ n1 value))))
+ ("m"
+ (let* ((add-months
+ (lambda (d n)
+ ;; Add N months to gregorian date D, i.e.,
+ ;; a list (MONTH DAY YEAR). Return a valid
+ ;; gregorian date.
+ (let ((m (+ (nth 0 d) n)))
+ (list (mod m 12)
+ (nth 1 d)
+ (+ (/ m 12) (nth 2 d))))))
+ (months ; Complete months to TARGET.
+ (* (/ (+ (* 12 (- (nth 2 target) (nth 2 base)))
+ (- (nth 0 target) (nth 0 base))
+ ;; If START's day is greater than
+ ;; TARGET's, remove incomplete month.
+ (if (> (nth 1 target) (nth 1 base)) 0 -1))
+ value)
+ value))
+ (before (funcall add-months base months)))
+ (setf n1 (calendar-absolute-from-gregorian before))
+ (setf n2
+ (calendar-absolute-from-gregorian
+ (funcall add-months before value)))))
+ (_
+ (let* ((d (nth 1 base))
+ (m (nth 0 base))
+ (y (nth 2 base))
+ (years ; Complete years to TARGET.
+ (* (/ (- (nth 2 target)
+ y
+ ;; If START's month and day are
+ ;; greater than TARGET's, remove
+ ;; incomplete year.
+ (if (or (> (nth 0 target) m)
+ (and (= (nth 0 target) m)
+ (> (nth 1 target) d)))
+ 0
+ 1))
+ value)
+ value))
+ (before (list m d (+ y years))))
+ (setf n1 (calendar-absolute-from-gregorian before))
+ (setf n2 (calendar-absolute-from-gregorian
+ (list m d (+ (nth 2 before) value)))))))
+ ;; Handle PREFER parameter, if any.
+ (cond
+ ((eq prefer 'past) (if (= cday n2) n2 n1))
+ ((eq prefer 'future) (if (= cday n1) n1 n2))
+ (t (if (> (abs (- cday n1)) (abs (- cday n2))) n2 n1)))))))))
+
+(defun org-date-to-gregorian (d)
+ "Turn any specification of date D into a Gregorian date for the calendar."
+ (cond ((integerp d) (calendar-gregorian-from-absolute d))
+ ((and (listp d) (= (length d) 3)) d)
+ ((stringp d)
+ (let ((d (org-parse-time-string d)))
+ (list (nth 4 d) (nth 3 d) (nth 5 d))))
+ ((listp d) (list (nth 4 d) (nth 3 d) (nth 5 d)))))
+
+(defun org-parse-time-string (s &optional nodefault zone)
+ "Parse the standard Org time string.
+
This should be a lot faster than the normal `parse-time-string'.
-If time is not given, defaults to 0:00. However, with optional NODEFAULT,
-hour and minute fields will be nil if not given."
+
+If time is not given, defaults to 0:00. However, with optional
+NODEFAULT, hour and minute fields will be nil if not given.
+
+The optional ZONE is omitted or nil for Emacs local time, t for
+Universal Time, ‘wall’ for system wall clock time, or a string as
+in the TZ environment variable."
(cond ((string-match org-ts-regexp0 s)
(list 0
- (if (or (match-beginning 8) (not nodefault))
- (string-to-number (or (match-string 8 s) "0")))
- (if (or (match-beginning 7) (not nodefault))
- (string-to-number (or (match-string 7 s) "0")))
+ (when (or (match-beginning 8) (not nodefault))
+ (string-to-number (or (match-string 8 s) "0")))
+ (when (or (match-beginning 7) (not nodefault))
+ (string-to-number (or (match-string 7 s) "0")))
(string-to-number (match-string 4 s))
(string-to-number (match-string 3 s))
(string-to-number (match-string 2 s))
- nil nil nil))
+ nil nil zone))
((string-match "^<[^>]+>$" s)
+ ;; FIXME: `decode-time' needs to be called with ZONE as its
+ ;; second argument. However, this requires at least Emacs
+ ;; 25.1. We can do it when we switch to this version as our
+ ;; minimal requirement.
(decode-time (seconds-to-time (org-matcher-time s))))
- (t (error "Not a standard Org-mode time string: %s" s))))
+ (t (error "Not a standard Org time string: %s" s))))
(defun org-timestamp-up (&optional arg)
"Increase the date item at the cursor by one.
@@ -17355,14 +18007,21 @@ With prefix ARG, change that many days."
(org-timestamp-change (- (prefix-numeric-value arg)) 'day) 'updown))
(defun org-at-timestamp-p (&optional inactive-ok)
- "Determine if the cursor is in or at a timestamp."
+ "Non-nil if point is inside a timestamp.
+
+When optional argument INACTIVE-OK is non-nil, also consider
+inactive timestamps.
+
+When this function returns a non-nil value, match data is set
+according to `org-ts-regexp3' or `org-ts-regexp2', depending on
+INACTIVE-OK."
(interactive)
(let* ((tsr (if inactive-ok org-ts-regexp3 org-ts-regexp2))
(pos (point))
(ans (or (looking-at tsr)
(save-excursion
(skip-chars-backward "^[<\n\r\t")
- (if (> (point) (point-min)) (backward-char 1))
+ (when (> (point) (point-min)) (backward-char 1))
(and (looking-at tsr)
(> (- (match-end 0) pos) -1))))))
(and ans
@@ -17403,8 +18062,8 @@ With prefix ARG, change that many days."
(defun org-at-clock-log-p nil
"Is the cursor on the clock log line?"
(save-excursion
- (move-beginning-of-line 1)
- (looking-at "^[ \t]*CLOCK:")))
+ (beginning-of-line)
+ (looking-at org-clock-line-re)))
(defvar org-clock-history) ; defined in org-clock.el
(defvar org-clock-adjust-closest nil) ; defined in org-clock.el
@@ -17420,19 +18079,19 @@ When SUPPRESS-TMP-DELAY is non-nil, suppress delays like \"--2d\"."
org-ts-what
extra rem
ts time time0 fixnext clrgx)
- (if (not (org-at-timestamp-p t))
- (user-error "Not at a timestamp"))
+ (unless (org-at-timestamp-p t)
+ (user-error "Not at a timestamp"))
(if (and (not what) (eq org-ts-what 'bracket))
(org-toggle-timestamp-type)
;; Point isn't on brackets. Remember the part of the time-stamp
;; the point was in. Indeed, size of time-stamps may change,
;; but point must be kept in the same category nonetheless.
(setq origin-cat org-ts-what)
- (if (and (not what) (not (eq org-ts-what 'day))
- org-display-custom-times
- (get-text-property (point) 'display)
- (not (get-text-property (1- (point)) 'display)))
- (setq org-ts-what 'day))
+ (when (and (not what) (not (eq org-ts-what 'day))
+ org-display-custom-times
+ (get-text-property (point) 'display)
+ (not (get-text-property (1- (point)) 'display)))
+ (setq org-ts-what 'day))
(setq org-ts-what (or what org-ts-what)
inactive (= (char-after (match-beginning 0)) ?\[)
ts (match-string 0))
@@ -17441,26 +18100,28 @@ When SUPPRESS-TMP-DELAY is non-nil, suppress delays like \"--2d\"."
"\\(\\(-[012][0-9]:[0-5][0-9]\\)?\\( +[.+]?-?[-+][0-9]+[hdwmy]\\(/[0-9]+[hdwmy]\\)?\\)*\\)[]>]"
ts)
(setq extra (match-string 1 ts))
- (if suppress-tmp-delay
- (setq extra (replace-regexp-in-string " --[0-9]+[hdwmy]" "" extra))))
- (if (string-match "^.\\{10\\}.*?[0-9]+:[0-9][0-9]" ts)
- (setq with-hm t))
+ (when suppress-tmp-delay
+ (setq extra (replace-regexp-in-string " --[0-9]+[hdwmy]" "" extra))))
+ (when (string-match "^.\\{10\\}.*?[0-9]+:[0-9][0-9]" ts)
+ (setq with-hm t))
(setq time0 (org-parse-time-string ts))
(when (and updown
(eq org-ts-what 'minute)
(not current-prefix-arg))
;; This looks like s-up and s-down. Change by one rounding step.
(setq n (* dm (cond ((> n 0) 1) ((< n 0) -1) (t 0))))
- (when (not (= 0 (setq rem (% (nth 1 time0) dm))))
+ (unless (= 0 (setq rem (% (nth 1 time0) dm)))
(setcar (cdr time0) (+ (nth 1 time0)
(if (> n 0) (- rem) (- dm rem))))))
(setq time
- (encode-time (or (car time0) 0)
- (+ (if (eq org-ts-what 'minute) n 0) (nth 1 time0))
- (+ (if (eq org-ts-what 'hour) n 0) (nth 2 time0))
- (+ (if (eq org-ts-what 'day) n 0) (nth 3 time0))
- (+ (if (eq org-ts-what 'month) n 0) (nth 4 time0))
- (+ (if (eq org-ts-what 'year) n 0) (nth 5 time0))))
+ (apply #'encode-time
+ (or (car time0) 0)
+ (+ (if (eq org-ts-what 'minute) n 0) (nth 1 time0))
+ (+ (if (eq org-ts-what 'hour) n 0) (nth 2 time0))
+ (+ (if (eq org-ts-what 'day) n 0) (nth 3 time0))
+ (+ (if (eq org-ts-what 'month) n 0) (nth 4 time0))
+ (+ (if (eq org-ts-what 'year) n 0) (nth 5 time0))
+ (nthcdr 6 time0)))
(when (and (member org-ts-what '(hour minute))
extra
(string-match "-\\([012][0-9]\\):\\([0-5][0-9]\\)" extra))
@@ -17470,15 +18131,15 @@ When SUPPRESS-TMP-DELAY is non-nil, suppress delays like \"--2d\"."
n dm)))
(when (integerp org-ts-what)
(setq extra (org-modify-ts-extra extra org-ts-what n dm)))
- (if (eq what 'calendar)
- (let ((cal-date (org-get-date-from-calendar)))
- (setcar (nthcdr 4 time0) (nth 0 cal-date)) ; month
- (setcar (nthcdr 3 time0) (nth 1 cal-date)) ; day
- (setcar (nthcdr 5 time0) (nth 2 cal-date)) ; year
- (setcar time0 (or (car time0) 0))
- (setcar (nthcdr 1 time0) (or (nth 1 time0) 0))
- (setcar (nthcdr 2 time0) (or (nth 2 time0) 0))
- (setq time (apply 'encode-time time0))))
+ (when (eq what 'calendar)
+ (let ((cal-date (org-get-date-from-calendar)))
+ (setcar (nthcdr 4 time0) (nth 0 cal-date)) ; month
+ (setcar (nthcdr 3 time0) (nth 1 cal-date)) ; day
+ (setcar (nthcdr 5 time0) (nth 2 cal-date)) ; year
+ (setcar time0 (or (car time0) 0))
+ (setcar (nthcdr 1 time0) (or (nth 1 time0) 0))
+ (setcar (nthcdr 2 time0) (or (nth 2 time0) 0))
+ (setq time (apply 'encode-time time0))))
;; Insert the new time-stamp, and ensure point stays in the same
;; category as before (i.e. not after the last position in that
;; category).
@@ -17489,17 +18150,21 @@ When SUPPRESS-TMP-DELAY is non-nil, suppress delays like \"--2d\"."
(goto-char pos))
(save-match-data
(looking-at org-ts-regexp3)
- (goto-char (cond
- ;; `day' category ends before `hour' if any, or at
- ;; the end of the day name.
- ((eq origin-cat 'day)
- (min (or (match-beginning 7) (1- (match-end 5))) origin))
- ((eq origin-cat 'hour) (min (match-end 7) origin))
- ((eq origin-cat 'minute) (min (1- (match-end 8)) origin))
- ((integerp origin-cat) (min (1- (match-end 0)) origin))
- ;; `year' and `month' have both fixed size: point
- ;; couldn't have moved into another part.
- (t origin))))
+ (goto-char
+ (pcase origin-cat
+ ;; `day' category ends before `hour' if any, or at the end
+ ;; of the day name.
+ (`day (min (or (match-beginning 7) (1- (match-end 5))) origin))
+ (`hour (min (match-end 7) origin))
+ (`minute (min (1- (match-end 8)) origin))
+ ((pred integerp) (min (1- (match-end 0)) origin))
+ ;; Point was right after the time-stamp. However, the
+ ;; time-stamp length might have changed, so refer to
+ ;; (match-end 0) instead.
+ (`after (match-end 0))
+ ;; `year' and `month' have both fixed size: point couldn't
+ ;; have moved into another part.
+ (_ origin))))
;; Update clock if on a CLOCK line.
(org-clock-update-time-maybe)
;; Maybe adjust the closest clock in `org-clock-history'
@@ -17508,11 +18173,12 @@ When SUPPRESS-TMP-DELAY is non-nil, suppress delays like \"--2d\"."
(< 1 (length (delq nil (mapcar 'marker-position
org-clock-history))))))
(message "No clock to adjust")
- (cond ((save-excursion ; fix previous clock?
+ (cond ((save-excursion ; fix previous clock?
(re-search-backward org-ts-regexp0 nil t)
- (org-looking-back (concat org-clock-string " \\[")))
+ (looking-back (concat org-clock-string " \\[")
+ (line-beginning-position)))
(setq fixnext 1 clrgx (concat org-ts-regexp0 "\\] =>.*$")))
- ((save-excursion ; fix next clock?
+ ((save-excursion ; fix next clock?
(re-search-backward org-ts-regexp0 nil t)
(looking-at (concat org-ts-regexp0 "\\] =>")))
(setq fixnext -1 clrgx (concat org-clock-string " \\[" org-ts-regexp0))))
@@ -17521,8 +18187,8 @@ When SUPPRESS-TMP-DELAY is non-nil, suppress delays like \"--2d\"."
(let* ((p (save-excursion (org-back-to-heading t)))
(cl (mapcar (lambda(c) (abs (- (marker-position c) p))) org-clock-history))
(clfixnth
- (+ fixnext (- (length cl) (or (length (member (apply #'min cl) cl)) 100))))
- (clfixpos (if (> 0 clfixnth) nil (nth clfixnth org-clock-history))))
+ (+ fixnext (- (length cl) (or (length (member (apply 'min cl) cl)) 100))))
+ (clfixpos (unless (> 0 clfixnth) (nth clfixnth org-clock-history))))
(if (not clfixpos)
(message "No clock to adjust")
(save-excursion
@@ -17536,10 +18202,10 @@ When SUPPRESS-TMP-DELAY is non-nil, suppress delays like \"--2d\"."
(file-name-nondirectory (buffer-file-name))
(org-get-heading t t)))))))))
;; Try to recenter the calendar window, if any.
- (if (and org-calendar-follow-timestamp-change
- (get-buffer-window "*Calendar*" t)
- (memq org-ts-what '(day month year)))
- (org-recenter-calendar (time-to-days time))))))
+ (when (and org-calendar-follow-timestamp-change
+ (get-buffer-window "*Calendar*" t)
+ (memq org-ts-what '(day month year)))
+ (org-recenter-calendar (time-to-days time))))))
(defun org-modify-ts-extra (s pos n dm)
"Change the different parts of the lead-time and repeat fields in timestamp."
@@ -17553,13 +18219,13 @@ When SUPPRESS-TMP-DELAY is non-nil, suppress delays like \"--2d\"."
h (string-to-number (match-string 2 s)))
(if (org-pos-in-match-range pos 2)
(setq h (+ h n))
- (setq n (* dm (org-no-warnings (signum n))))
- (when (not (= 0 (setq rem (% m dm))))
+ (setq n (* dm (with-no-warnings (signum n))))
+ (unless (= 0 (setq rem (% m dm)))
(setq m (+ m (if (> n 0) (- rem) (- dm rem)))))
(setq m (+ m n)))
- (if (< m 0) (setq m (+ m 60) h (1- h)))
- (if (> m 59) (setq m (- m 60) h (1+ h)))
- (setq h (min 24 (max 0 h)))
+ (when (< m 0) (setq m (+ m 60) h (1- h)))
+ (when (> m 59) (setq m (- m 60) h (1+ h)))
+ (setq h (mod h 24))
(setq ng 1 new (format "-%02d:%02d" h m)))
((org-pos-in-match-range pos 6)
(setq ng 6 new (car (rassoc (+ n (cdr (assoc (match-string 6 s) idx))) idx))))
@@ -17578,14 +18244,14 @@ When SUPPRESS-TMP-DELAY is non-nil, suppress delays like \"--2d\"."
(substring s (match-end ng))))))
s))
-(defun org-recenter-calendar (date)
- "If the calendar is visible, recenter it to DATE."
+(defun org-recenter-calendar (d)
+ "If the calendar is visible, recenter it to date D."
(let ((cwin (get-buffer-window "*Calendar*" t)))
(when cwin
(let ((calendar-move-hook nil))
(with-selected-window cwin
- (calendar-goto-date (if (listp date) date
- (calendar-gregorian-from-absolute date))))))))
+ (calendar-goto-date
+ (if (listp d) d (calendar-gregorian-from-absolute d))))))))
(defun org-goto-calendar (&optional arg)
"Go to the Emacs calendar at the current date.
@@ -17596,17 +18262,17 @@ A prefix ARG can be used to force the current date."
(calendar-move-hook nil)
(calendar-view-holidays-initially-flag nil)
(calendar-view-diary-initially-flag nil))
- (if (or (org-at-timestamp-p)
- (save-excursion
- (beginning-of-line 1)
- (looking-at (concat ".*" tsr))))
- (let ((d1 (time-to-days (current-time)))
- (d2 (time-to-days
- (org-time-string-to-time (match-string 1)))))
- (setq diff (- d2 d1))))
+ (when (or (org-at-timestamp-p)
+ (save-excursion
+ (beginning-of-line 1)
+ (looking-at (concat ".*" tsr))))
+ (let ((d1 (time-to-days (current-time)))
+ (d2 (time-to-days
+ (org-time-string-to-time (match-string 1)))))
+ (setq diff (- d2 d1))))
(calendar)
(calendar-goto-today)
- (if (and diff (not arg)) (calendar-forward-day diff))))
+ (when (and diff (not arg)) (calendar-forward-day diff))))
(defun org-get-date-from-calendar ()
"Return a list (month day year) of date at point in calendar."
@@ -17625,7 +18291,8 @@ If there is already a time stamp at the cursor position, update it."
(encode-time 0 0 0 (nth 1 cal-date) (car cal-date) (nth 2 cal-date))))))
(defcustom org-effort-durations
- `(("h" . 60)
+ `(("min" . 1)
+ ("h" . 60)
("d" . ,(* 60 8))
("w" . ,(* 60 8 5))
("m" . ,(* 60 8 5 4))
@@ -17641,7 +18308,8 @@ minutes.
For example, if the value of this variable is ((\"hours\" . 60)), then an
effort string \"2hours\" is equivalent to 120 minutes."
:group 'org-agenda
- :version "24.1"
+ :version "26.1"
+ :package-version '(Org . "8.3")
:type '(alist :key-type (string :tag "Modifier")
:value-type (number :tag "Minutes")))
@@ -17734,10 +18402,6 @@ The format is determined by `org-time-clocksum-format',
;; return formatted time duration
clocksum))))
-(defalias 'org-minutes-to-hh:mm-string 'org-minutes-to-clocksum-string)
-(make-obsolete 'org-minutes-to-hh:mm-string 'org-minutes-to-clocksum-string
- "Org mode version 8.0")
-
(defun org-hours-to-clocksum-string (n)
(org-minutes-to-clocksum-string (* n 60)))
@@ -17793,19 +18457,21 @@ tables are not re-aligned, etc."
:version "24.3"
:group 'org-agenda)
-(defcustom org-agenda-ignore-drawer-properties nil
+(defcustom org-agenda-ignore-properties nil
"Avoid updating text properties when building the agenda.
-Properties are used to prepare buffers for effort estimates, appointments,
-and subtree-local categories.
-If you don't use these in the agenda, you can add them to this list and
-agenda building will be a bit faster.
+Properties are used to prepare buffers for effort estimates,
+appointments, statistics and subtree-local categories.
+If you don't use these in the agenda, you can add them to this
+list and agenda building will be a bit faster.
The value is a list, with zero or more of the symbols `effort', `appt',
-or `category'."
+`stats' or `category'."
:type '(set :greedy t
(const effort)
(const appt)
+ (const stats)
(const category))
- :version "24.3"
+ :version "26.1"
+ :package-version '(Org . "8.3")
:group 'org-agenda)
(defun org-duration-string-to-minutes (s &optional output-to-string)
@@ -17821,25 +18487,25 @@ Entries containing a colon are interpreted as H:MM by
(regexp-opt (mapcar 'car org-effort-durations))
"\\)")))
(while (string-match re s)
- (incf result (* (cdr (assoc (match-string 2 s) org-effort-durations))
- (string-to-number (match-string 1 s))))
+ (cl-incf result (* (cdr (assoc (match-string 2 s) org-effort-durations))
+ (string-to-number (match-string 1 s))))
(setq s (replace-match "" nil t s)))
(setq result (floor result))
- (incf result (org-hh:mm-string-to-minutes s))
+ (cl-incf result (org-hh:mm-string-to-minutes s))
(if output-to-string (number-to-string result) result)))
;;;; Files
(defun org-save-all-org-buffers ()
- "Save all Org-mode buffers without user confirmation."
+ "Save all Org buffers without user confirmation."
(interactive)
- (message "Saving all Org-mode buffers...")
+ (message "Saving all Org buffers...")
(save-some-buffers t (lambda () (derived-mode-p 'org-mode)))
(when (featurep 'org-id) (org-id-locations-save))
- (message "Saving all Org-mode buffers... done"))
+ (message "Saving all Org buffers... done"))
(defun org-revert-all-org-buffers ()
- "Revert all Org-mode buffers.
+ "Revert all Org buffers.
Prompt for confirmation when there are unsaved changes.
Be sure you know what you are doing before letting this function
overwrite your changes.
@@ -17856,13 +18522,11 @@ changes from another. I believe the procedure must be like this:
(user-error "Abort"))
(save-excursion
(save-window-excursion
- (mapc
- (lambda (b)
- (when (and (with-current-buffer b (derived-mode-p 'org-mode))
- (with-current-buffer b buffer-file-name))
- (org-pop-to-buffer-same-window b)
- (revert-buffer t 'no-confirm)))
- (buffer-list))
+ (dolist (b (buffer-list))
+ (when (and (with-current-buffer b (derived-mode-p 'org-mode))
+ (with-current-buffer b buffer-file-name))
+ (pop-to-buffer-same-window b)
+ (revert-buffer t 'no-confirm)))
(when (and (featurep 'org-id) org-id-track-globally)
(org-id-locations-load)))))
@@ -17871,29 +18535,19 @@ changes from another. I believe the procedure must be like this:
;;;###autoload
(defun org-switchb (&optional arg)
"Switch between Org buffers.
-With one prefix argument, restrict available buffers to files.
-With two prefix arguments, restrict available buffers to agenda files.
-Defaults to `iswitchb' for buffer name completion.
-Set `org-completion-use-ido' to make it use ido instead."
+With `\\[universal-argument]' prefix, restrict available buffers to files.
+
+With `\\[universal-argument] \\[universal-argument]' \
+prefix, restrict available buffers to agenda files."
(interactive "P")
- (let ((blist (cond ((equal arg '(4)) (org-buffer-list 'files))
- ((equal arg '(16)) (org-buffer-list 'agenda))
- (t (org-buffer-list))))
- (org-completion-use-iswitchb org-completion-use-iswitchb)
- (org-completion-use-ido org-completion-use-ido))
- (unless (or org-completion-use-ido org-completion-use-iswitchb)
- (setq org-completion-use-iswitchb t))
- (org-pop-to-buffer-same-window
- (org-icompleting-read "Org buffer: "
- (mapcar 'list (mapcar 'buffer-name blist))
- nil t))))
-
-;;; Define some older names previously used for this functionality
-;;;###autoload
-(defalias 'org-ido-switchb 'org-switchb)
-;;;###autoload
-(defalias 'org-iswitchb 'org-switchb)
+ (let ((blist (org-buffer-list
+ (cond ((equal arg '(4)) 'files)
+ ((equal arg '(16)) 'agenda)))))
+ (pop-to-buffer-same-window
+ (completing-read "Org buffer: "
+ (mapcar #'list (mapcar #'buffer-name blist))
+ nil t))))
(defun org-buffer-list (&optional predicate exclude-tmp)
"Return a list of Org buffers.
@@ -17968,8 +18622,10 @@ used by the agenda files. If ARCHIVE is `ifmode', do this only if
"Return non-nil, if FILE is an agenda file.
If FILE is omitted, use the file associated with the current
buffer."
- (member (or file (buffer-file-name))
- (org-agenda-files t)))
+ (let ((fname (or file (buffer-file-name))))
+ (and fname
+ (member (file-truename fname)
+ (mapcar #'file-truename (org-agenda-files t))))))
(defun org-edit-agenda-file-list ()
"Edit the list of agenda files.
@@ -17981,15 +18637,15 @@ the buffer and restores the previous window configuration."
(if (stringp org-agenda-files)
(let ((cw (current-window-configuration)))
(find-file org-agenda-files)
- (org-set-local 'org-window-configuration cw)
- (org-add-hook 'after-save-hook
- (lambda ()
- (set-window-configuration
- (prog1 org-window-configuration
- (kill-buffer (current-buffer))))
- (org-install-agenda-files-menu)
- (message "New agenda file list installed"))
- nil 'local)
+ (setq-local org-window-configuration cw)
+ (add-hook 'after-save-hook
+ (lambda ()
+ (set-window-configuration
+ (prog1 org-window-configuration
+ (kill-buffer (current-buffer))))
+ (org-install-agenda-files-menu)
+ (message "New agenda file list installed"))
+ nil 'local)
(message "%s" (substitute-command-keys
"Edit list and finish with \\[save-buffer]")))
(customize-variable 'org-agenda-files)))
@@ -18039,19 +18695,16 @@ un-expanded file names."
If the current buffer visits an agenda file, find the next one in the list.
If the current buffer does not, find the first agenda file."
(interactive)
- (let* ((fs (org-agenda-files t))
- (files (append fs (list (car fs))))
- (tcf (if buffer-file-name (file-truename buffer-file-name)))
+ (let* ((fs (or (org-agenda-files t)
+ (user-error "No agenda files")))
+ (files (copy-sequence fs))
+ (tcf (and buffer-file-name (file-truename buffer-file-name)))
file)
- (unless files (user-error "No agenda files"))
- (catch 'exit
- (dolist (file files)
- (if (equal (file-truename file) tcf)
- (when (car files)
- (find-file (car files))
- (throw 'exit t))))
- (find-file (car fs)))
- (if (buffer-base-buffer) (org-pop-to-buffer-same-window (buffer-base-buffer)))))
+ (when tcf
+ (while (and (setq file (pop files))
+ (not (equal (file-truename file) tcf)))))
+ (find-file (car (or files fs)))
+ (when (buffer-base-buffer) (pop-to-buffer-same-window (buffer-base-buffer)))))
(defun org-agenda-file-to-front (&optional to-end)
"Move/add the current file to the top of the agenda file list.
@@ -18069,7 +18722,7 @@ end of the list."
x had)
(setq x (assoc ctf file-alist) had x)
- (if (not x) (setq x (cons ctf (abbreviate-file-name buffer-file-name))))
+ (unless x (setq x (cons ctf (abbreviate-file-name buffer-file-name))))
(if to-end
(setq file-alist (append (delq x file-alist) (list x)))
(setq file-alist (cons x (delq x file-alist))))
@@ -18090,15 +18743,15 @@ Optional argument FILE means use this file instead of the current."
(afile (abbreviate-file-name file))
(files (delq nil (mapcar
(lambda (x)
- (if (equal true-file
- (file-truename x))
- nil x))
+ (unless (equal true-file
+ (file-truename x))
+ x))
(org-agenda-files t)))))
(if (not (= (length files) (length (org-agenda-files t))))
(progn
(org-store-new-agenda-file-list files)
(org-install-agenda-files-menu)
- (message "Removed file: %s" afile))
+ (message "Removed from Org Agenda list: %s" afile))
(message "File was not in list: %s (not removed)" afile))))
(defun org-file-menu-entry (file)
@@ -18106,7 +18759,7 @@ Optional argument FILE means use this file instead of the current."
(defun org-check-agenda-file (file)
"Make sure FILE exists. If not, ask user what to do."
- (when (not (file-exists-p file))
+ (unless (file-exists-p file)
(message "Non-existent agenda file %s. [R]emove from list or [A]bort?"
(abbreviate-file-name file))
(let ((r (downcase (read-char-exclusive))))
@@ -18114,17 +18767,18 @@ Optional argument FILE means use this file instead of the current."
((equal r ?r)
(org-remove-file file)
(throw 'nextfile t))
- (t (error "Abort"))))))
+ (t (user-error "Abort"))))))
(defun org-get-agenda-file-buffer (file)
- "Get a buffer visiting FILE. If the buffer needs to be created, add
-it to the list of buffers which might be released later."
+ "Get an agenda buffer visiting FILE.
+If the buffer needs to be created, add it to the list of buffers
+which might be released later."
(let ((buf (org-find-base-buffer-visiting file)))
(if buf
buf ; just return it
;; Make a new buffer and remember it
(setq buf (find-file-noselect file))
- (if buf (push buf org-agenda-new-buffers))
+ (when buf (push buf org-agenda-new-buffers))
buf)))
(defun org-release-buffers (blist)
@@ -18149,7 +18803,7 @@ When a buffer is unmodified, it is just killed. When modified, it is saved
(inhibit-read-only t)
(org-inhibit-startup org-agenda-inhibit-startup)
(rea (concat ":" org-archive-tag ":"))
- file re pos)
+ re pos)
(setq org-tag-alist-for-agenda nil
org-tag-groups-alist-for-agenda nil)
(save-excursion
@@ -18161,20 +18815,15 @@ When a buffer is unmodified, it is just killed. When modified, it is saved
(org-check-agenda-file file)
(set-buffer (org-get-agenda-file-buffer file)))
(widen)
- (org-set-regexps-and-options-for-tags)
+ (org-set-regexps-and-options 'tags-only)
(setq pos (point))
- (goto-char (point-min))
- (let ((case-fold-search t))
- (when (search-forward "#+setupfile" nil t)
- ;; Don't set all regexps and options systematically as
- ;; this is only run for setting agenda tags from setup
- ;; file
- (org-set-regexps-and-options)))
- (or (memq 'category org-agenda-ignore-drawer-properties)
+ (or (memq 'category org-agenda-ignore-properties)
(org-refresh-category-properties))
- (or (memq 'effort org-agenda-ignore-drawer-properties)
- (org-refresh-properties org-effort-property 'org-effort))
- (or (memq 'appt org-agenda-ignore-drawer-properties)
+ (or (memq 'stats org-agenda-ignore-properties)
+ (org-refresh-stats-properties))
+ (or (memq 'effort org-agenda-ignore-properties)
+ (org-refresh-effort-properties))
+ (or (memq 'appt org-agenda-ignore-properties)
(org-refresh-properties "APPT_WARNTIME" 'org-appt-warntime))
(setq org-todo-keywords-for-agenda
(append org-todo-keywords-for-agenda org-todo-keywords-1))
@@ -18182,31 +18831,32 @@ When a buffer is unmodified, it is just killed. When modified, it is saved
(append org-done-keywords-for-agenda org-done-keywords))
(setq org-todo-keyword-alist-for-agenda
(append org-todo-keyword-alist-for-agenda org-todo-key-alist))
- (setq org-drawers-for-agenda
- (append org-drawers-for-agenda org-drawers))
(setq org-tag-alist-for-agenda
(org-uniquify
(append org-tag-alist-for-agenda
- org-tag-alist
- org-tag-persistent-alist)))
- (if org-group-tags
- (setq org-tag-groups-alist-for-agenda
- (org-uniquify-alist
- (append org-tag-groups-alist-for-agenda org-tag-groups-alist))))
+ org-current-tag-alist)))
+ ;; Merge current file's tag groups into global
+ ;; `org-tag-groups-alist-for-agenda'.
+ (when org-group-tags
+ (dolist (alist org-tag-groups-alist)
+ (let ((old (assoc (car alist) org-tag-groups-alist-for-agenda)))
+ (if old
+ (setcdr old (org-uniquify (append (cdr old) (cdr alist))))
+ (push alist org-tag-groups-alist-for-agenda)))))
(org-with-silent-modifications
(save-excursion
(remove-text-properties (point-min) (point-max) pall)
(when org-agenda-skip-archived-trees
(goto-char (point-min))
(while (re-search-forward rea nil t)
- (if (org-at-heading-p t)
- (add-text-properties (point-at-bol) (org-end-of-subtree t) pa))))
+ (when (org-at-heading-p t)
+ (add-text-properties (point-at-bol) (org-end-of-subtree t) pa))))
(goto-char (point-min))
- (setq re (format org-heading-keyword-regexp-format
- org-comment-string))
+ (setq re (format "^\\*+ .*\\<%s\\>" org-comment-string))
(while (re-search-forward re nil t)
- (add-text-properties
- (match-beginning 0) (org-end-of-subtree t) pc))))
+ (when (save-match-data (org-in-commented-heading-p t))
+ (add-text-properties
+ (match-beginning 0) (org-end-of-subtree t) pc)))))
(goto-char pos)))))
(setq org-todo-keywords-for-agenda
(org-uniquify org-todo-keywords-for-agenda))
@@ -18223,7 +18873,7 @@ When a buffer is unmodified, it is just killed. When modified, it is saved
(org-defkey org-cdlatex-mode-map "^" 'org-cdlatex-underscore-caret)
(org-defkey org-cdlatex-mode-map "`" 'cdlatex-math-symbol)
(org-defkey org-cdlatex-mode-map "'" 'org-cdlatex-math-modify)
-(org-defkey org-cdlatex-mode-map "\C-c{" 'cdlatex-environment)
+(org-defkey org-cdlatex-mode-map "\C-c{" 'org-cdlatex-environment-indent)
(defvar org-cdlatex-texmathp-advice-is-done nil
"Flag remembering if we have applied the advice to texmathp already.")
@@ -18231,7 +18881,7 @@ When a buffer is unmodified, it is just killed. When modified, it is saved
(define-minor-mode org-cdlatex-mode
"Toggle the minor `org-cdlatex-mode'.
This mode supports entering LaTeX environment and math in LaTeX fragments
-in Org-mode.
+in Org mode.
\\{org-cdlatex-mode-map}"
nil " OCDL" nil
(when org-cdlatex-mode
@@ -18241,11 +18891,11 @@ in Org-mode.
(unless org-cdlatex-texmathp-advice-is-done
(setq org-cdlatex-texmathp-advice-is-done t)
(defadvice texmathp (around org-math-always-on activate)
- "Always return t in org-mode buffers.
+ "Always return t in Org buffers.
This is because we want to insert math symbols without dollars even outside
-the LaTeX math segments. If Orgmode thinks that point is actually inside
-an embedded LaTeX fragment, let texmathp do its job.
-\\[org-cdlatex-mode-map]"
+the LaTeX math segments. If Org mode thinks that point is actually inside
+an embedded LaTeX fragment, let `texmathp' do its job.
+`\\[org-cdlatex-mode-map]'"
(interactive)
(let (p)
(cond
@@ -18257,8 +18907,8 @@ an embedded LaTeX fragment, let texmathp do its job.
(let ((p (org-inside-LaTeX-fragment-p)))
(if (and p (member (car p) (plist-get org-format-latex-options :matchers)))
(setq ad-return-value t
- texmathp-why '("Org-mode embedded math" . 0))
- (if p ad-do-it)))))))))
+ texmathp-why '("Org mode embedded math" . 0))
+ (when p ad-do-it)))))))))
(defun turn-on-org-cdlatex ()
"Unconditionally turn on `org-cdlatex-mode'."
@@ -18283,7 +18933,7 @@ It makes sense to do so if `org-cdlatex-mode' is active and if the cursor is
(cdlatex-tab) t)
((org-inside-LaTeX-fragment-p) (cdlatex-tab) t))))
-(defun org-cdlatex-underscore-caret (&optional arg)
+(defun org-cdlatex-underscore-caret (&optional _arg)
"Execute `cdlatex-sub-superscript' in LaTeX fragments.
Revert to the normal definition outside of these fragments."
(interactive "P")
@@ -18292,7 +18942,7 @@ Revert to the normal definition outside of these fragments."
(let (org-cdlatex-mode)
(call-interactively (key-binding (vector last-input-event))))))
-(defun org-cdlatex-math-modify (&optional arg)
+(defun org-cdlatex-math-modify (&optional _arg)
"Execute `cdlatex-math-modify' in LaTeX fragments.
Revert to the normal definition outside of these fragments."
(interactive "P")
@@ -18301,21 +18951,66 @@ Revert to the normal definition outside of these fragments."
(let (org-cdlatex-mode)
(call-interactively (key-binding (vector last-input-event))))))
+(defun org-cdlatex-environment-indent (&optional environment item)
+ "Execute `cdlatex-environment' and indent the inserted environment.
+
+ENVIRONMENT and ITEM are passed to `cdlatex-environment'.
+
+The inserted environment is indented to current indentation
+unless point is at the beginning of the line, in which the
+environment remains unintended."
+ (interactive)
+ ;; cdlatex-environment always return nil. Therefore, capture output
+ ;; first and determine if an environment was selected.
+ (let* ((beg (point-marker))
+ (end (copy-marker (point) t))
+ (inserted (progn
+ (ignore-errors (cdlatex-environment environment item))
+ (< beg end)))
+ ;; Figure out how many lines to move forward after the
+ ;; environment has been inserted.
+ (lines (when inserted
+ (save-excursion
+ (- (cl-loop while (< beg (point))
+ with x = 0
+ do (forward-line -1)
+ (cl-incf x)
+ finally return x)
+ (if (progn (goto-char beg)
+ (and (progn (skip-chars-forward " \t") (eolp))
+ (progn (skip-chars-backward " \t") (bolp))))
+ 1 0)))))
+ (env (org-trim (delete-and-extract-region beg end))))
+ (when inserted
+ ;; Get indentation of next line unless at column 0.
+ (let ((ind (if (bolp) 0
+ (save-excursion
+ (org-return-indent)
+ (prog1 (org-get-indentation)
+ (when (progn (skip-chars-forward " \t") (eolp))
+ (delete-region beg (point)))))))
+ (bol (progn (skip-chars-backward " \t") (bolp))))
+ ;; Insert a newline before environment unless at column zero
+ ;; to "escape" the current line. Insert a newline if
+ ;; something is one the same line as \end{ENVIRONMENT}.
+ (insert
+ (concat (unless bol "\n") env
+ (when (and (skip-chars-forward " \t") (not (eolp))) "\n")))
+ (unless (zerop ind)
+ (save-excursion
+ (goto-char beg)
+ (while (< (point) end)
+ (unless (eolp) (indent-line-to ind))
+ (forward-line))))
+ (goto-char beg)
+ (forward-line lines)
+ (indent-line-to ind)))
+ (set-marker beg nil)
+ (set-marker end nil)))
;;;; LaTeX fragments
-(defvar org-latex-regexps
- '(("begin" "^[ \t]*\\(\\\\begin{\\([a-zA-Z0-9\\*]+\\)[^\000]+?\\\\end{\\2}\\)" 1 t)
- ;; ("$" "\\([ (]\\|^\\)\\(\\(\\([$]\\)\\([^ \r\n,.$].*?\\(\n.*?\\)\\{0,5\\}[^ \r\n,.$]\\)\\4\\)\\)\\([ .,?;:'\")]\\|$\\)" 2 nil)
- ;; \000 in the following regex is needed for org-inside-LaTeX-fragment-p
- ("$1" "\\([^$]\\|^\\)\\(\\$[^ \r\n,;.$]\\$\\)\\([- .,?;:'\")\000]\\|$\\)" 2 nil)
- ("$" "\\([^$]\\|^\\)\\(\\(\\$\\([^ \r\n,;.$][^$\n\r]*?\\(\n[^$\n\r]*?\\)\\{0,2\\}[^ \r\n,.$]\\)\\$\\)\\)\\([- .,?;:'\")\000]\\|$\\)" 2 nil)
- ("\\(" "\\\\([^\000]*?\\\\)" 0 nil)
- ("\\[" "\\\\\\[[^\000]*?\\\\\\]" 0 nil)
- ("$$" "\\$\\$[^\000]*?\\$\\$" 0 nil))
- "Regular expressions for matching embedded LaTeX.")
-
(defun org-inside-LaTeX-fragment-p ()
"Test if point is inside a LaTeX fragment.
I.e. after a \\begin, \\(, \\[, $, or $$, without the corresponding closing
@@ -18358,7 +19053,7 @@ looks only before point, not after."
(while (re-search-backward "\\$\\$" lim t)
(setq dd-on (not dd-on)))
(goto-char pos)
- (if dd-on (cons "$$" m))))))
+ (when dd-on (cons "$$" m))))))
(defun org-inside-latex-macro-p ()
"Is point inside a LaTeX macro or its arguments?"
@@ -18366,179 +19061,226 @@ looks only before point, not after."
(org-in-regexp
"\\\\[a-zA-Z]+\\*?\\(\\(\\[[^][\n{}]*\\]\\)\\|\\({[^{}\n]*}\\)\\)*")))
-(defvar org-latex-fragment-image-overlays nil
- "List of overlays carrying the images of latex fragments.")
-(make-variable-buffer-local 'org-latex-fragment-image-overlays)
+(defun org--format-latex-make-overlay (beg end image &optional imagetype)
+ "Build an overlay between BEG and END using IMAGE file.
+Argument IMAGETYPE is the extension of the displayed image,
+as a string. It defaults to \"png\"."
+ (let ((ov (make-overlay beg end))
+ (imagetype (or (intern imagetype) 'png)))
+ (overlay-put ov 'org-overlay-type 'org-latex-overlay)
+ (overlay-put ov 'evaporate t)
+ (overlay-put ov
+ 'modification-hooks
+ (list (lambda (o _flag _beg _end &optional _l)
+ (delete-overlay o))))
+ (overlay-put ov
+ 'display
+ (list 'image :type imagetype :file image :ascent 'center))))
+
+(defun org--list-latex-overlays (&optional beg end)
+ "List all Org LaTeX overlays in current buffer.
+Limit to overlays between BEG and END when those are provided."
+ (cl-remove-if-not
+ (lambda (o) (eq (overlay-get o 'org-overlay-type) 'org-latex-overlay))
+ (overlays-in (or beg (point-min)) (or end (point-max)))))
+
+(defun org-remove-latex-fragment-image-overlays (&optional beg end)
+ "Remove all overlays with LaTeX fragment images in current buffer.
+When optional arguments BEG and END are non-nil, remove all
+overlays between them instead. Return a non-nil value when some
+overlays were removed, nil otherwise."
+ (let ((overlays (org--list-latex-overlays beg end)))
+ (mapc #'delete-overlay overlays)
+ overlays))
+
+(defun org-toggle-latex-fragment (&optional arg)
+ "Preview the LaTeX fragment at point, or all locally or globally.
-(defun org-remove-latex-fragment-image-overlays ()
- "Remove all overlays with LaTeX fragment images in current buffer."
- (mapc 'delete-overlay org-latex-fragment-image-overlays)
- (setq org-latex-fragment-image-overlays nil))
+If the cursor is on a LaTeX fragment, create the image and overlay
+it over the source code, if there is none. Remove it otherwise.
+If there is no fragment at point, display all fragments in the
+current section.
-(defun org-preview-latex-fragment (&optional subtree)
- "Preview the LaTeX fragment at point, or all locally or globally.
-If the cursor is in a LaTeX fragment, create the image and overlay
-it over the source code. If there is no fragment at point, display
-all fragments in the current text, from one headline to the next. With
-prefix SUBTREE, display all fragments in the current subtree. With a
-double prefix arg \\[universal-argument] \\[universal-argument], or when \
-the cursor is before the first headline,
-display all fragments in the buffer.
-The images can be removed again with \\[org-ctrl-c-ctrl-c]."
+With prefix ARG, preview or clear image for all fragments in the
+current subtree or in the whole buffer when used before the first
+headline. With a prefix ARG `\\[universal-argument] \
+\\[universal-argument]' preview or clear images
+for all fragments in the buffer."
(interactive "P")
- (unless buffer-file-name
- (user-error "Can't preview LaTeX fragment in a non-file buffer"))
(when (display-graphic-p)
- (org-remove-latex-fragment-image-overlays)
- (save-excursion
- (save-restriction
- (let (beg end at msg)
+ (catch 'exit
+ (save-excursion
+ (let (beg end msg)
(cond
- ((or (equal subtree '(16))
- (not (save-excursion
- (re-search-backward org-outline-regexp-bol nil t))))
- (setq beg (point-min) end (point-max)
- msg "Creating images for buffer...%s"))
- ((equal subtree '(4))
- (org-back-to-heading)
- (setq beg (point) end (org-end-of-subtree t)
- msg "Creating images for subtree...%s"))
+ ((or (equal arg '(16))
+ (and (equal arg '(4))
+ (org-with-limited-levels (org-before-first-heading-p))))
+ (if (org-remove-latex-fragment-image-overlays)
+ (progn (message "LaTeX fragments images removed from buffer")
+ (throw 'exit nil))
+ (setq msg "Creating images for buffer...")))
+ ((equal arg '(4))
+ (org-with-limited-levels (org-back-to-heading t))
+ (setq beg (point))
+ (setq end (progn (org-end-of-subtree t) (point)))
+ (if (org-remove-latex-fragment-image-overlays beg end)
+ (progn
+ (message "LaTeX fragment images removed from subtree")
+ (throw 'exit nil))
+ (setq msg "Creating images for subtree...")))
+ ((let ((datum (org-element-context)))
+ (when (memq (org-element-type datum)
+ '(latex-environment latex-fragment))
+ (setq beg (org-element-property :begin datum))
+ (setq end (org-element-property :end datum))
+ (if (org-remove-latex-fragment-image-overlays beg end)
+ (progn (message "LaTeX fragment image removed")
+ (throw 'exit nil))
+ (setq msg "Creating image...")))))
(t
- (if (setq at (org-inside-LaTeX-fragment-p))
- (goto-char (max (point-min) (- (cdr at) 2)))
- (org-back-to-heading))
- (setq beg (point) end (progn (outline-next-heading) (point))
- msg (if at "Creating image...%s"
- "Creating images for entry...%s"))))
- (message msg "")
- (narrow-to-region beg end)
- (goto-char beg)
- (org-format-latex
- (concat org-latex-preview-ltxpng-directory (file-name-sans-extension
- (file-name-nondirectory
- buffer-file-name)))
- default-directory 'overlays msg at 'forbuffer
- org-latex-create-formula-image-program)
- (message msg "done. Use `C-c C-c' to remove images."))))))
-
-(defun org-format-latex (prefix &optional dir overlays msg at
- forbuffer processing-type)
- "Replace LaTeX fragments with links to an image, and produce images.
+ (org-with-limited-levels
+ (setq beg (if (org-at-heading-p) (line-beginning-position)
+ (outline-previous-heading)
+ (point)))
+ (setq end (progn (outline-next-heading) (point)))
+ (if (org-remove-latex-fragment-image-overlays beg end)
+ (progn
+ (message "LaTeX fragment images removed from section")
+ (throw 'exit nil))
+ (setq msg "Creating images for section...")))))
+ (let ((file (buffer-file-name (buffer-base-buffer))))
+ (org-format-latex
+ (concat org-preview-latex-image-directory "org-ltximg")
+ beg end
+ ;; Emacs cannot overlay images from remote hosts. Create
+ ;; it in `temporary-file-directory' instead.
+ (if (or (not file) (file-remote-p file))
+ temporary-file-directory
+ default-directory)
+ 'overlays msg 'forbuffer org-preview-latex-default-process))
+ (message (concat msg "done")))))))
+
+(defun org-format-latex
+ (prefix &optional beg end dir overlays msg forbuffer processing-type)
+ "Replace LaTeX fragments with links to an image.
+
+The function takes care of creating the replacement image.
+
+Only consider fragments between BEG and END when those are
+provided.
+
+When optional argument OVERLAYS is non-nil, display the image on
+top of the fragment instead of replacing it.
+
+PROCESSING-TYPE is the conversion method to use, as a symbol.
+
Some of the options can be changed using the variable
-`org-format-latex-options'."
- (if (and overlays (fboundp 'clear-image-cache)) (clear-image-cache))
- (let* ((prefixnodir (file-name-nondirectory prefix))
- (absprefix (expand-file-name prefix dir))
- (todir (file-name-directory absprefix))
- (opt org-format-latex-options)
- (optnew org-format-latex-options)
- (matchers (plist-get opt :matchers))
- (re-list org-latex-regexps)
- (cnt 0) txt hash link beg end re checkdir
- string
- m n block-type block linkfile movefile ov)
- ;; Check the different regular expressions
- (dolist (e re-list)
- (setq m (car e) re (nth 1 e) n (nth 2 e) block-type (nth 3 e)
- block (if block-type "\n\n" ""))
- (when (member m matchers)
- (goto-char (point-min))
- (while (re-search-forward re nil t)
- (when (and (or (not at) (equal (cdr at) (match-beginning n)))
- (or (not overlays)
- (not (eq (get-char-property (match-beginning n)
- 'org-overlay-type)
- 'org-latex-overlay))))
- (cond
- ((eq processing-type 'verbatim))
- ((eq processing-type 'mathjax)
- ;; Prepare for MathJax processing.
- (setq string (match-string n))
- (when (member m '("$" "$1"))
- (save-excursion
- (delete-region (match-beginning n) (match-end n))
- (goto-char (match-beginning n))
- (insert (concat "\\(" (substring string 1 -1) "\\)")))))
- ((or (eq processing-type 'dvipng)
- (eq processing-type 'imagemagick))
- ;; Process to an image.
- (setq txt (match-string n)
- beg (match-beginning n) end (match-end n)
- cnt (1+ cnt))
- (let ((face (face-at-point))
- (fg (plist-get opt :foreground))
- (bg (plist-get opt :background))
- ;; Ensure full list is printed.
- print-length print-level)
- (when forbuffer
- ;; Get the colors from the face at point.
+`org-format-latex-options', which see."
+ (when (and overlays (fboundp 'clear-image-cache)) (clear-image-cache))
+ (unless (eq processing-type 'verbatim)
+ (let* ((math-regexp "\\$\\|\\\\[([]\\|^[ \t]*\\\\begin{[A-Za-z0-9*]+}")
+ (cnt 0)
+ checkdir-flag)
+ (goto-char (or beg (point-min)))
+ ;; Optimize overlay creation: (info "(elisp) Managing Overlays").
+ (when (and overlays (memq processing-type '(dvipng imagemagick)))
+ (overlay-recenter (or end (point-max))))
+ (while (re-search-forward math-regexp end t)
+ (unless (and overlays
+ (eq (get-char-property (point) 'org-overlay-type)
+ 'org-latex-overlay))
+ (let* ((context (org-element-context))
+ (type (org-element-type context)))
+ (when (memq type '(latex-environment latex-fragment))
+ (let ((block-type (eq type 'latex-environment))
+ (value (org-element-property :value context))
+ (beg (org-element-property :begin context))
+ (end (save-excursion
+ (goto-char (org-element-property :end context))
+ (skip-chars-backward " \r\t\n")
+ (point))))
+ (cond
+ ((eq processing-type 'mathjax)
+ ;; Prepare for MathJax processing.
+ (if (not (string-match "\\`\\$\\$?" value))
+ (goto-char end)
+ (delete-region beg end)
+ (if (string= (match-string 0 value) "$$")
+ (insert "\\[" (substring value 2 -2) "\\]")
+ (insert "\\(" (substring value 1 -1) "\\)"))))
+ ((assq processing-type org-preview-latex-process-alist)
+ ;; Process to an image.
+ (cl-incf cnt)
(goto-char beg)
- (when (eq fg 'auto)
- (setq fg (face-attribute face :foreground nil 'default)))
- (when (eq bg 'auto)
- (setq bg (face-attribute face :background nil 'default)))
- (setq optnew (copy-sequence opt))
- (plist-put optnew :foreground fg)
- (plist-put optnew :background bg))
- (setq hash (sha1 (prin1-to-string
- (list org-format-latex-header
- org-latex-default-packages-alist
- org-latex-packages-alist
- org-format-latex-options
- forbuffer txt fg bg)))
- linkfile (format "%s_%s.png" prefix hash)
- movefile (format "%s_%s.png" absprefix hash)))
- (setq link (concat block "[[file:" linkfile "]]" block))
- (if msg (message msg cnt))
- (goto-char beg)
- (unless checkdir ; Ensure the directory exists.
- (setq checkdir t)
- (or (file-directory-p todir) (make-directory todir t)))
- (unless (file-exists-p movefile)
- (org-create-formula-image
- txt movefile optnew forbuffer processing-type))
- (if overlays
- (progn
- (mapc (lambda (o)
- (if (eq (overlay-get o 'org-overlay-type)
- 'org-latex-overlay)
- (delete-overlay o)))
- (overlays-in beg end))
- (setq ov (make-overlay beg end))
- (overlay-put ov 'org-overlay-type 'org-latex-overlay)
- (if (featurep 'xemacs)
+ (let* ((processing-info
+ (cdr (assq processing-type org-preview-latex-process-alist)))
+ (face (face-at-point))
+ ;; Get the colors from the face at point.
+ (fg
+ (let ((color (plist-get org-format-latex-options
+ :foreground)))
+ (if (and forbuffer (eq color 'auto))
+ (face-attribute face :foreground nil 'default)
+ color)))
+ (bg
+ (let ((color (plist-get org-format-latex-options
+ :background)))
+ (if (and forbuffer (eq color 'auto))
+ (face-attribute face :background nil 'default)
+ color)))
+ (hash (sha1 (prin1-to-string
+ (list org-format-latex-header
+ org-latex-default-packages-alist
+ org-latex-packages-alist
+ org-format-latex-options
+ forbuffer value fg bg))))
+ (imagetype (or (plist-get processing-info :image-output-type) "png"))
+ (absprefix (expand-file-name prefix dir))
+ (linkfile (format "%s_%s.%s" prefix hash imagetype))
+ (movefile (format "%s_%s.%s" absprefix hash imagetype))
+ (sep (and block-type "\n\n"))
+ (link (concat sep "[[file:" linkfile "]]" sep))
+ (options
+ (org-combine-plists
+ org-format-latex-options
+ `(:foreground ,fg :background ,bg))))
+ (when msg (message msg cnt))
+ (unless checkdir-flag ; Ensure the directory exists.
+ (setq checkdir-flag t)
+ (let ((todir (file-name-directory absprefix)))
+ (unless (file-directory-p todir)
+ (make-directory todir t))))
+ (unless (file-exists-p movefile)
+ (org-create-formula-image
+ value movefile options forbuffer processing-type))
+ (if overlays
(progn
- (overlay-put ov 'invisible t)
- (overlay-put
- ov 'end-glyph
- (make-glyph (vector 'png :file movefile))))
- (overlay-put
- ov 'display
- (list 'image :type 'png :file movefile :ascent 'center)))
- (push ov org-latex-fragment-image-overlays)
- (goto-char end))
- (delete-region beg end)
- (insert (org-add-props link
- (list 'org-latex-src
- (replace-regexp-in-string
- "\"" "" txt)
- 'org-latex-src-embed-type
- (if block-type 'paragraph 'character))))))
- ((eq processing-type 'mathml)
- ;; Process to MathML
- (unless (save-match-data (org-format-latex-mathml-available-p))
- (user-error "LaTeX to MathML converter not configured"))
- (setq txt (match-string n)
- beg (match-beginning n) end (match-end n)
- cnt (1+ cnt))
- (if msg (message msg cnt))
- (goto-char beg)
- (delete-region beg end)
- (insert (org-format-latex-as-mathml
- txt block-type prefix dir)))
- (t
- (error "Unknown conversion type %s for LaTeX fragments"
- processing-type)))))))))
+ (dolist (o (overlays-in beg end))
+ (when (eq (overlay-get o 'org-overlay-type)
+ 'org-latex-overlay)
+ (delete-overlay o)))
+ (org--format-latex-make-overlay beg end movefile imagetype)
+ (goto-char end))
+ (delete-region beg end)
+ (insert
+ (org-add-props link
+ (list 'org-latex-src
+ (replace-regexp-in-string "\"" "" value)
+ 'org-latex-src-embed-type
+ (if block-type 'paragraph 'character)))))))
+ ((eq processing-type 'mathml)
+ ;; Process to MathML.
+ (unless (org-format-latex-mathml-available-p)
+ (user-error "LaTeX to MathML converter not configured"))
+ (cl-incf cnt)
+ (when msg (message msg cnt))
+ (goto-char beg)
+ (delete-region beg end)
+ (insert (org-format-latex-as-mathml
+ value block-type prefix dir)))
+ (t
+ (error "Unknown conversion process %s for LaTeX fragments"
+ processing-type)))))))))))
(defun org-create-math-formula (latex-frag &optional mathml-file)
"Convert LATEX-FRAG to MathML and store it in MATHML-FILE.
@@ -18553,20 +19295,25 @@ inspection."
(buffer-substring-no-properties
(region-beginning) (region-end)))))
(read-string "LaTeX Fragment: " frag nil frag))))
- (unless latex-frag (error "Invalid LaTeX fragment"))
- (let* ((tmp-in-file (file-relative-name
- (make-temp-name (expand-file-name "ltxmathml-in"))))
- (ignore (write-region latex-frag nil tmp-in-file))
+ (unless latex-frag (user-error "Invalid LaTeX fragment"))
+ (let* ((tmp-in-file
+ (let ((file (file-relative-name
+ (make-temp-name (expand-file-name "ltxmathml-in")))))
+ (write-region latex-frag nil file)
+ file))
(tmp-out-file (file-relative-name
(make-temp-name (expand-file-name "ltxmathml-out"))))
(cmd (format-spec
org-latex-to-mathml-convert-command
- `((?j . ,(shell-quote-argument
- (expand-file-name org-latex-to-mathml-jar-file)))
+ `((?j . ,(and org-latex-to-mathml-jar-file
+ (shell-quote-argument
+ (expand-file-name
+ org-latex-to-mathml-jar-file))))
(?I . ,(shell-quote-argument tmp-in-file))
+ (?i . ,latex-frag)
(?o . ,(shell-quote-argument tmp-out-file)))))
mathml shell-command-output)
- (when (org-called-interactively-p 'any)
+ (when (called-interactively-p 'any)
(unless (org-format-latex-mathml-available-p)
(user-error "LaTeX to MathML converter not configured")))
(message "Running %s" cmd)
@@ -18576,11 +19323,10 @@ inspection."
(with-current-buffer (find-file-noselect tmp-out-file t)
(goto-char (point-min))
(when (re-search-forward
- (concat
- (regexp-quote
- "")) nil t)
+ (format ""
+ (regexp-quote
+ "xmlns=\"http://www.w3.org/1998/Math/MathML\""))
+ nil t)
(prog1 (match-string 0) (kill-buffer))))))
(cond
(mathml
@@ -18588,7 +19334,7 @@ inspection."
(concat "\n" mathml))
(when mathml-file
(write-region mathml nil mathml-file))
- (when (org-called-interactively-p 'any)
+ (when (called-interactively-p 'any)
(message mathml)))
((message "LaTeX to MathML conversion failed")
(message shell-command-output)))
@@ -18627,186 +19373,117 @@ inspection."
;; Failed conversion. Return the LaTeX fragment verbatim
latex-frag)))
-(defun org-create-formula-image (string tofile options buffer &optional type)
- "Create an image from LaTeX source using dvipng or convert.
-This function calls either `org-create-formula-image-with-dvipng'
-or `org-create-formula-image-with-imagemagick' depending on the
-value of `org-latex-create-formula-image-program' or on the value
-of the optional TYPE variable.
-
-Note: ultimately these two function should be combined as they
-share a good deal of logic."
- (org-check-external-command
- "latex" "needed to convert LaTeX fragments to images")
- (funcall
- (case (or type org-latex-create-formula-image-program)
- ('dvipng
- (org-check-external-command
- "dvipng" "needed to convert LaTeX fragments to images")
- #'org-create-formula-image-with-dvipng)
- ('imagemagick
- (org-check-external-command
- "convert" "you need to install imagemagick")
- #'org-create-formula-image-with-imagemagick)
- (t (error
- "Invalid value of `org-latex-create-formula-image-program'")))
- string tofile options buffer))
-
-(declare-function org-export-get-backend "ox" (name))
-(declare-function org-export--get-global-options "ox" (&optional backend))
-(declare-function org-export--get-inbuffer-options "ox" (&optional backend))
-(declare-function org-latex-guess-inputenc "ox-latex" (header))
-(declare-function org-latex-guess-babel-language "ox-latex" (header info))
-(defun org-create-formula--latex-header ()
- "Return LaTeX header appropriate for previewing a LaTeX snippet."
- (let ((info (org-combine-plists (org-export--get-global-options
- (org-export-get-backend 'latex))
- (org-export--get-inbuffer-options
- (org-export-get-backend 'latex)))))
- (org-latex-guess-babel-language
- (org-latex-guess-inputenc
- (org-splice-latex-header
- org-format-latex-header
- org-latex-default-packages-alist
- org-latex-packages-alist t
- (plist-get info :latex-header)))
- info)))
-
-;; This function borrows from Ganesh Swami's latex2png.el
-(defun org-create-formula-image-with-dvipng (string tofile options buffer)
- "This calls dvipng."
- (require 'ox-latex)
- (let* ((tmpdir (if (featurep 'xemacs)
- (temp-directory)
- temporary-file-directory))
+(defun org--get-display-dpi ()
+ "Get the DPI of the display.
+The function assumes that the display has the same pixel width in
+the horizontal and vertical directions."
+ (if (display-graphic-p)
+ (round (/ (display-pixel-height)
+ (/ (display-mm-height) 25.4)))
+ (error "Attempt to calculate the dpi of a non-graphic display")))
+
+(defun org-create-formula-image
+ (string tofile options buffer &optional processing-type)
+ "Create an image from LaTeX source using external processes.
+
+The LaTeX STRING is saved to a temporary LaTeX file, then
+converted to an image file by process PROCESSING-TYPE defined in
+`org-preview-latex-process-alist'. A nil value defaults to
+`org-preview-latex-default-process'.
+
+The generated image file is eventually moved to TOFILE.
+
+The OPTIONS argument controls the size, foreground color and
+background color of the generated image.
+
+When BUFFER non-nil, this function is used for LaTeX previewing.
+Otherwise, it is used to deal with LaTeX snippets showed in
+a HTML file."
+ (let* ((processing-type (or processing-type
+ org-preview-latex-default-process))
+ (processing-info
+ (cdr (assq processing-type org-preview-latex-process-alist)))
+ (programs (plist-get processing-info :programs))
+ (error-message (or (plist-get processing-info :message) ""))
+ (use-xcolor (plist-get processing-info :use-xcolor))
+ (image-input-type (plist-get processing-info :image-input-type))
+ (image-output-type (plist-get processing-info :image-output-type))
+ (post-clean (or (plist-get processing-info :post-clean)
+ '(".dvi" ".xdv" ".pdf" ".tex" ".aux" ".log"
+ ".svg" ".png" ".jpg" ".jpeg" ".out")))
+ (latex-header
+ (or (plist-get processing-info :latex-header)
+ (org-latex-make-preamble
+ (org-export-get-environment (org-export-get-backend 'latex))
+ org-format-latex-header
+ 'snippet)))
+ (latex-compiler (plist-get processing-info :latex-compiler))
+ (image-converter (plist-get processing-info :image-converter))
+ (tmpdir temporary-file-directory)
(texfilebase (make-temp-name
(expand-file-name "orgtex" tmpdir)))
(texfile (concat texfilebase ".tex"))
- (dvifile (concat texfilebase ".dvi"))
- (pngfile (concat texfilebase ".png"))
- (fnh (if (featurep 'xemacs)
- (font-height (face-font 'default))
- (face-attribute 'default :height nil)))
- (scale (or (plist-get options (if buffer :scale :html-scale)) 1.0))
- (dpi (number-to-string (* scale (floor (* 0.9 (if buffer fnh 140.))))))
+ (image-size-adjust (or (plist-get processing-info :image-size-adjust)
+ '(1.0 . 1.0)))
+ (scale (* (if buffer (car image-size-adjust) (cdr image-size-adjust))
+ (or (plist-get options (if buffer :scale :html-scale)) 1.0)))
+ (dpi (* scale (if buffer (org--get-display-dpi) 140.0)))
(fg (or (plist-get options (if buffer :foreground :html-foreground))
"Black"))
(bg (or (plist-get options (if buffer :background :html-background))
- "Transparent")))
- (if (eq fg 'default) (setq fg (org-dvipng-color :foreground))
- (unless (string= fg "Transparent") (setq fg (org-dvipng-color-format fg))))
- (if (eq bg 'default) (setq bg (org-dvipng-color :background))
- (unless (string= bg "Transparent") (setq bg (org-dvipng-color-format bg))))
- (let ((latex-header (org-create-formula--latex-header)))
+ "Transparent"))
+ (log-buf (get-buffer-create "*Org Preview LaTeX Output*"))
+ (resize-mini-windows nil)) ;Fix Emacs flicker when creating image.
+ (dolist (program programs)
+ (org-check-external-command program error-message))
+ (if use-xcolor
+ (progn (if (eq fg 'default)
+ (setq fg (org-latex-color :foreground))
+ (setq fg (org-latex-color-format fg)))
+ (if (eq bg 'default)
+ (setq bg (org-latex-color :background))
+ (setq bg (org-latex-color-format
+ (if (string= bg "Transparent") "white" bg))))
+ (with-temp-file texfile
+ (insert latex-header)
+ (insert "\n\\begin{document}\n"
+ "\\definecolor{fg}{rgb}{" fg "}\n"
+ "\\definecolor{bg}{rgb}{" bg "}\n"
+ "\n\\pagecolor{bg}\n"
+ "\n{\\color{fg}\n"
+ string
+ "\n}\n"
+ "\n\\end{document}\n")))
+ (if (eq fg 'default)
+ (setq fg (org-dvipng-color :foreground))
+ (unless (string= fg "Transparent")
+ (setq fg (org-dvipng-color-format fg))))
+ (if (eq bg 'default)
+ (setq bg (org-dvipng-color :background))
+ (unless (string= bg "Transparent")
+ (setq bg (org-dvipng-color-format bg))))
(with-temp-file texfile
(insert latex-header)
(insert "\n\\begin{document}\n" string "\n\\end{document}\n")))
- (let ((dir default-directory))
- (condition-case nil
- (progn
- (cd tmpdir)
- (call-process "latex" nil nil nil texfile))
- (error nil))
- (cd dir))
- (if (not (file-exists-p dvifile))
- (progn (message "Failed to create dvi file from %s" texfile) nil)
- (condition-case nil
- (if (featurep 'xemacs)
- (call-process "dvipng" nil nil nil
- "-fg" fg "-bg" bg
- "-T" "tight"
- "-o" pngfile
- dvifile)
- (call-process "dvipng" nil nil nil
- "-fg" fg "-bg" bg
- "-D" dpi
- ;;"-x" scale "-y" scale
- "-T" "tight"
- "-o" pngfile
- dvifile))
- (error nil))
- (if (not (file-exists-p pngfile))
- (if org-format-latex-signal-error
- (error "Failed to create png file from %s" texfile)
- (message "Failed to create png file from %s" texfile)
- nil)
- ;; Use the requested file name and clean up
- (copy-file pngfile tofile 'replace)
- (loop for e in '(".dvi" ".tex" ".aux" ".log" ".png" ".out") do
- (if (file-exists-p (concat texfilebase e))
- (delete-file (concat texfilebase e))))
- pngfile))))
-
-(declare-function org-latex-compile "ox-latex" (texfile &optional snippet))
-(defun org-create-formula-image-with-imagemagick (string tofile options buffer)
- "This calls convert, which is included into imagemagick."
- (require 'ox-latex)
- (let* ((tmpdir (if (featurep 'xemacs)
- (temp-directory)
- temporary-file-directory))
- (texfilebase (make-temp-name
- (expand-file-name "orgtex" tmpdir)))
- (texfile (concat texfilebase ".tex"))
- (pdffile (concat texfilebase ".pdf"))
- (pngfile (concat texfilebase ".png"))
- (fnh (if (featurep 'xemacs)
- (font-height (face-font 'default))
- (face-attribute 'default :height nil)))
- (scale (or (plist-get options (if buffer :scale :html-scale)) 1.0))
- (dpi (number-to-string (* scale (floor (if buffer fnh 120.)))))
- (fg (or (plist-get options (if buffer :foreground :html-foreground))
- "black"))
- (bg (or (plist-get options (if buffer :background :html-background))
- "white")))
- (if (eq fg 'default) (setq fg (org-latex-color :foreground))
- (setq fg (org-latex-color-format fg)))
- (if (eq bg 'default) (setq bg (org-latex-color :background))
- (setq bg (org-latex-color-format
- (if (string= bg "Transparent") "white" bg))))
- (let ((latex-header (org-create-formula--latex-header)))
- (with-temp-file texfile
- (insert latex-header)
- (insert "\n\\begin{document}\n"
- "\\definecolor{fg}{rgb}{" fg "}\n"
- "\\definecolor{bg}{rgb}{" bg "}\n"
- "\n\\pagecolor{bg}\n"
- "\n{\\color{fg}\n"
- string
- "\n}\n"
- "\n\\end{document}\n")))
- (org-latex-compile texfile t)
- (if (not (file-exists-p pdffile))
- (progn (message "Failed to create pdf file from %s" texfile) nil)
- (condition-case nil
- (if (featurep 'xemacs)
- (call-process "convert" nil nil nil
- "-density" "96"
- "-trim"
- "-antialias"
- pdffile
- "-quality" "100"
- ;; "-sharpen" "0x1.0"
- pngfile)
- (call-process "convert" nil nil nil
- "-density" dpi
- "-trim"
- "-antialias"
- pdffile
- "-quality" "100"
- ;; "-sharpen" "0x1.0"
- pngfile))
- (error nil))
- (if (not (file-exists-p pngfile))
- (if org-format-latex-signal-error
- (error "Failed to create png file from %s" texfile)
- (message "Failed to create png file from %s" texfile)
- nil)
- ;; Use the requested file name and clean up
- (copy-file pngfile tofile 'replace)
- (loop for e in '(".pdf" ".tex" ".aux" ".log" ".png") do
- (if (file-exists-p (concat texfilebase e))
- (delete-file (concat texfilebase e))))
- pngfile))))
+
+ (let* ((err-msg (format "Please adjust '%s' part of \
+`org-preview-latex-process-alist'."
+ processing-type))
+ (image-input-file
+ (org-compile-file
+ texfile latex-compiler image-input-type err-msg log-buf))
+ (image-output-file
+ (org-compile-file
+ image-input-file image-converter image-output-type err-msg log-buf
+ `((?F . ,(shell-quote-argument fg))
+ (?B . ,(shell-quote-argument bg))
+ (?D . ,(shell-quote-argument (format "%s" dpi)))
+ (?S . ,(shell-quote-argument (format "%s" (/ dpi 140.0))))))))
+ (copy-file image-output-file tofile 'replace)
+ (dolist (e post-clean)
+ (when (file-exists-p (concat texfilebase e))
+ (delete-file (concat texfilebase e))))
+ image-output-file)))
(defun org-splice-latex-header (tpl def-pkg pkg snippets-p &optional extra)
"Fill a LaTeX header template TPL.
@@ -18830,22 +19507,22 @@ SNIPPETS-P indicates if this is run to create snippet images for HTML."
(setq rpl (if (or (match-end 1) (not def-pkg))
"" (org-latex-packages-to-string def-pkg snippets-p t))
tpl (replace-match rpl t t tpl))
- (if def-pkg (setq end (org-latex-packages-to-string def-pkg snippets-p))))
+ (when def-pkg (setq end (org-latex-packages-to-string def-pkg snippets-p))))
(if (string-match "\\[\\(NO-\\)?PACKAGES\\][ \t]*\n?" tpl)
(setq rpl (if (or (match-end 1) (not pkg))
"" (org-latex-packages-to-string pkg snippets-p t))
tpl (replace-match rpl t t tpl))
- (if pkg (setq end
- (concat end "\n"
- (org-latex-packages-to-string pkg snippets-p)))))
+ (when pkg (setq end
+ (concat end "\n"
+ (org-latex-packages-to-string pkg snippets-p)))))
(if (string-match "\\[\\(NO-\\)?EXTRA\\][ \t]*\n?" tpl)
(setq rpl (if (or (match-end 1) (not extra))
"" (concat extra "\n"))
tpl (replace-match rpl t t tpl))
- (if (and extra (string-match "\\S-" extra))
- (setq end (concat end "\n" extra))))
+ (when (and extra (string-match "\\S-" extra))
+ (setq end (concat end "\n" extra))))
(if (string-match "\\S-" end)
(concat tpl "\n" end)
@@ -18869,35 +19546,21 @@ SNIPPETS-P indicates if this is run to create snippet images for HTML."
(defun org-dvipng-color (attr)
"Return a RGB color specification for dvipng."
- (apply 'format "rgb %s %s %s"
- (mapcar 'org-normalize-color
- (if (featurep 'xemacs)
- (color-rgb-components
- (face-property 'default
- (cond ((eq attr :foreground) 'foreground)
- ((eq attr :background) 'background))))
- (color-values (face-attribute 'default attr nil))))))
+ (org-dvipng-color-format (face-attribute 'default attr nil)))
(defun org-dvipng-color-format (color-name)
"Convert COLOR-NAME to a RGB color value for dvipng."
- (apply 'format "rgb %s %s %s"
+ (apply #'format "rgb %s %s %s"
(mapcar 'org-normalize-color
- (color-values color-name))))
+ (color-values color-name))))
(defun org-latex-color (attr)
"Return a RGB color for the LaTeX color package."
- (apply 'format "%s,%s,%s"
- (mapcar 'org-normalize-color
- (if (featurep 'xemacs)
- (color-rgb-components
- (face-property 'default
- (cond ((eq attr :foreground) 'foreground)
- ((eq attr :background) 'background))))
- (color-values (face-attribute 'default attr nil))))))
+ (org-latex-color-format (face-attribute 'default attr nil)))
(defun org-latex-color-format (color-name)
"Convert COLOR-NAME to a RGB color value."
- (apply 'format "%s,%s,%s"
+ (apply #'format "%s,%s,%s"
(mapcar 'org-normalize-color
(color-values color-name))))
@@ -18909,8 +19572,7 @@ SNIPPETS-P indicates if this is run to create snippet images for HTML."
;; Image display
-(defvar org-inline-image-overlays nil)
-(make-variable-buffer-local 'org-inline-image-overlays)
+(defvar-local org-inline-image-overlays nil)
(defun org-toggle-inline-images (&optional include-linked)
"Toggle the display of inline images.
@@ -18919,13 +19581,14 @@ INCLUDE-LINKED is passed to `org-display-inline-images'."
(if org-inline-image-overlays
(progn
(org-remove-inline-images)
- (message "Inline image display turned off"))
+ (when (called-interactively-p 'interactive)
+ (message "Inline image display turned off")))
(org-display-inline-images include-linked)
- (if (and (org-called-interactively-p)
- org-inline-image-overlays)
- (message "%d images displayed inline"
- (length org-inline-image-overlays))
- (message "No images to display inline"))))
+ (when (called-interactively-p 'interactive)
+ (message (if org-inline-image-overlays
+ (format "%d images displayed inline"
+ (length org-inline-image-overlays))
+ "No images to display inline")))))
(defun org-redisplay-inline-images ()
"Refresh the display of inline images."
@@ -18937,68 +19600,116 @@ INCLUDE-LINKED is passed to `org-display-inline-images'."
(defun org-display-inline-images (&optional include-linked refresh beg end)
"Display inline images.
-Normally only links without a description part are inlined, because this
-is how it will work for export. When INCLUDE-LINKED is set, also links
-with a description part will be inlined. This can be nice for a quick
-look at those images, but it does not reflect what exported files will look
-like.
-When REFRESH is set, refresh existing images between BEG and END.
-This will create new image displays only if necessary.
-BEG and END default to the buffer boundaries."
+
+An inline image is a link which follows either of these
+conventions:
+
+ 1. Its path is a file with an extension matching return value
+ from `image-file-name-regexp' and it has no contents.
+
+ 2. Its description consists in a single link of the previous
+ type.
+
+When optional argument INCLUDE-LINKED is non-nil, also links with
+a text description part will be inlined. This can be nice for
+a quick look at those images, but it does not reflect what
+exported files will look like.
+
+When optional argument REFRESH is non-nil, refresh existing
+images between BEG and END. This will create new image displays
+only if necessary. BEG and END default to the buffer
+boundaries."
(interactive "P")
(when (display-graphic-p)
(unless refresh
(org-remove-inline-images)
- (if (fboundp 'clear-image-cache) (clear-image-cache)))
- (save-excursion
- (save-restriction
- (widen)
- (setq beg (or beg (point-min)) end (or end (point-max)))
- (goto-char beg)
- (let ((re (concat "\\[\\[\\(\\(file:\\)\\|\\([./~]\\)\\)\\([^]\n]+?"
- (substring (org-image-file-name-regexp) 0 -2)
- "\\)\\]" (if include-linked "" "\\]")))
- (case-fold-search t)
- old file ov img type attrwidth width)
- (while (re-search-forward re end t)
- (setq old (get-char-property-and-overlay (match-beginning 1)
- 'org-image-overlay)
- file (expand-file-name
- (concat (or (match-string 3) "") (match-string 4))))
- (when (image-type-available-p 'imagemagick)
- (setq attrwidth (if (or (listp org-image-actual-width)
- (null org-image-actual-width))
- (save-excursion
- (save-match-data
- (when (re-search-backward
- "#\\+attr.*:width[ \t]+\\([^ ]+\\)"
- (save-excursion
- (re-search-backward "^[ \t]*$\\|\\`" nil t)) t)
- (string-to-number (match-string 1))))))
- width (cond ((eq org-image-actual-width t) nil)
- ((null org-image-actual-width) attrwidth)
- ((numberp org-image-actual-width)
- org-image-actual-width)
- ((listp org-image-actual-width)
- (or attrwidth (car org-image-actual-width))))
- type (if width 'imagemagick)))
- (when (file-exists-p file)
- (if (and (car-safe old) refresh)
- (image-refresh (overlay-get (cdr old) 'display))
- (setq img (save-match-data (create-image file type nil :width width)))
- (when img
- (setq ov (make-overlay (match-beginning 0) (match-end 0)))
- (overlay-put ov 'display img)
- (overlay-put ov 'face 'default)
- (overlay-put ov 'org-image-overlay t)
- (overlay-put ov 'modification-hooks
- (list 'org-display-inline-remove-overlay))
- (push ov org-inline-image-overlays))))))))))
-
-(define-obsolete-function-alias
- 'org-display-inline-modification-hook 'org-display-inline-remove-overlay "24.3")
-
-(defun org-display-inline-remove-overlay (ov after beg end &optional len)
+ (when (fboundp 'clear-image-cache) (clear-image-cache)))
+ (org-with-wide-buffer
+ (goto-char (or beg (point-min)))
+ (let ((case-fold-search t)
+ (file-extension-re (image-file-name-regexp)))
+ (while (re-search-forward "[][]\\[\\(?:file\\|[./~]\\)" end t)
+ (let ((link (save-match-data (org-element-context))))
+ ;; Check if we're at an inline image.
+ (when (and (equal (org-element-property :type link) "file")
+ (or include-linked
+ (not (org-element-property :contents-begin link)))
+ (let ((parent (org-element-property :parent link)))
+ (or (not (eq (org-element-type parent) 'link))
+ (not (cdr (org-element-contents parent)))))
+ (string-match-p file-extension-re
+ (org-element-property :path link)))
+ (let ((file (expand-file-name
+ (org-link-unescape
+ (org-element-property :path link)))))
+ (when (file-exists-p file)
+ (let ((width
+ ;; Apply `org-image-actual-width' specifications.
+ (cond
+ ((not (image-type-available-p 'imagemagick)) nil)
+ ((eq org-image-actual-width t) nil)
+ ((listp org-image-actual-width)
+ (or
+ ;; First try to find a width among
+ ;; attributes associated to the paragraph
+ ;; containing link.
+ (let ((paragraph
+ (let ((e link))
+ (while (and (setq e (org-element-property
+ :parent e))
+ (not (eq (org-element-type e)
+ 'paragraph))))
+ e)))
+ (when paragraph
+ (save-excursion
+ (goto-char (org-element-property :begin paragraph))
+ (when
+ (re-search-forward
+ "^[ \t]*#\\+attr_.*?: +.*?:width +\\(\\S-+\\)"
+ (org-element-property
+ :post-affiliated paragraph)
+ t)
+ (string-to-number (match-string 1))))))
+ ;; Otherwise, fall-back to provided number.
+ (car org-image-actual-width)))
+ ((numberp org-image-actual-width)
+ org-image-actual-width)))
+ (old (get-char-property-and-overlay
+ (org-element-property :begin link)
+ 'org-image-overlay)))
+ (if (and (car-safe old) refresh)
+ (image-refresh (overlay-get (cdr old) 'display))
+ (let ((image (create-image file
+ (and width 'imagemagick)
+ nil
+ :width width)))
+ (when image
+ (let* ((link
+ ;; If inline image is the description
+ ;; of another link, be sure to
+ ;; consider the latter as the one to
+ ;; apply the overlay on.
+ (let ((parent
+ (org-element-property :parent link)))
+ (if (eq (org-element-type parent) 'link)
+ parent
+ link)))
+ (ov (make-overlay
+ (org-element-property :begin link)
+ (progn
+ (goto-char
+ (org-element-property :end link))
+ (skip-chars-backward " \t")
+ (point)))))
+ (overlay-put ov 'display image)
+ (overlay-put ov 'face 'default)
+ (overlay-put ov 'org-image-overlay t)
+ (overlay-put
+ ov 'modification-hooks
+ (list 'org-display-inline-remove-overlay))
+ (push ov org-inline-image-overlays)))))))))))))))
+
+(defun org-display-inline-remove-overlay (ov after _beg _end &optional _len)
"Remove inline-display overlay if a corresponding region is modified."
(let ((inhibit-modification-hooks t))
(when (and ov after)
@@ -19008,7 +19719,7 @@ BEG and END default to the buffer boundaries."
(defun org-remove-inline-images ()
"Remove inline display of images."
(interactive)
- (mapc 'delete-overlay org-inline-image-overlays)
+ (mapc #'delete-overlay org-inline-image-overlays)
(setq org-inline-image-overlays nil))
;;;; Key bindings
@@ -19016,44 +19727,46 @@ BEG and END default to the buffer boundaries."
;; Outline functions from `outline-mode-prefix-map'
;; that can be remapped in Org:
(define-key org-mode-map [remap outline-mark-subtree] 'org-mark-subtree)
-(define-key org-mode-map [remap show-subtree] 'org-show-subtree)
+(define-key org-mode-map [remap outline-show-subtree] 'org-show-subtree)
(define-key org-mode-map [remap outline-forward-same-level]
'org-forward-heading-same-level)
(define-key org-mode-map [remap outline-backward-same-level]
'org-backward-heading-same-level)
-(define-key org-mode-map [remap show-branches]
+(define-key org-mode-map [remap outline-show-branches]
'org-kill-note-or-show-branches)
(define-key org-mode-map [remap outline-promote] 'org-promote-subtree)
(define-key org-mode-map [remap outline-demote] 'org-demote-subtree)
(define-key org-mode-map [remap outline-insert-heading] 'org-ctrl-c-ret)
+(define-key org-mode-map [remap outline-next-visible-heading]
+ 'org-next-visible-heading)
+(define-key org-mode-map [remap outline-previous-visible-heading]
+ 'org-previous-visible-heading)
+(define-key org-mode-map [remap show-children] 'org-show-children)
;; Outline functions from `outline-mode-prefix-map' that can not
;; be remapped in Org:
-;;
+
;; - the column "key binding" shows whether the Outline function is still
;; available in Org mode on the same key that it has been bound to in
;; Outline mode:
;; - "overridden": key used for a different functionality in Org mode
;; - else: key still bound to the same Outline function in Org mode
-;;
-;; | Outline function | key binding | Org replacement |
-;; |------------------------------------+-------------+-----------------------|
-;; | `outline-next-visible-heading' | `C-c C-n' | still same function |
-;; | `outline-previous-visible-heading' | `C-c C-p' | still same function |
-;; | `outline-up-heading' | `C-c C-u' | still same function |
-;; | `outline-move-subtree-up' | overridden | better: org-shiftup |
-;; | `outline-move-subtree-down' | overridden | better: org-shiftdown |
-;; | `show-entry' | overridden | no replacement |
-;; | `show-children' | `C-c C-i' | visibility cycling |
-;; | `show-branches' | `C-c C-k' | still same function |
-;; | `show-subtree' | overridden | visibility cycling |
-;; | `show-all' | overridden | no replacement |
-;; | `hide-subtree' | overridden | visibility cycling |
-;; | `hide-body' | overridden | no replacement |
-;; | `hide-entry' | overridden | visibility cycling |
-;; | `hide-leaves' | overridden | no replacement |
-;; | `hide-sublevels' | overridden | no replacement |
-;; | `hide-other' | overridden | no replacement |
+
+;; | Outline function | key binding | Org replacement |
+;; |------------------------------------+-------------+--------------------------|
+;; | `outline-up-heading' | `C-c C-u' | still same function |
+;; | `outline-move-subtree-up' | overridden | better: org-shiftup |
+;; | `outline-move-subtree-down' | overridden | better: org-shiftdown |
+;; | `show-entry' | overridden | no replacement |
+;; | `show-branches' | `C-c C-k' | still same function |
+;; | `show-subtree' | overridden | visibility cycling |
+;; | `show-all' | overridden | no replacement |
+;; | `hide-subtree' | overridden | visibility cycling |
+;; | `hide-body' | overridden | no replacement |
+;; | `hide-entry' | overridden | visibility cycling |
+;; | `hide-leaves' | overridden | no replacement |
+;; | `hide-sublevels' | overridden | no replacement |
+;; | `hide-other' | overridden | no replacement |
;; Make `C-c C-x' a prefix key
(org-defkey org-mode-map "\C-c\C-x" (make-sparse-keymap))
@@ -19064,8 +19777,7 @@ BEG and END default to the buffer boundaries."
(org-defkey org-mode-map [(control tab)] 'org-force-cycle-archived)
(org-defkey org-mode-map "\M-\t" #'pcomplete)
;; The following line is necessary under Suse GNU/Linux
-(unless (featurep 'xemacs)
- (org-defkey org-mode-map [S-iso-lefttab] 'org-shifttab))
+(org-defkey org-mode-map [S-iso-lefttab] 'org-shifttab)
(org-defkey org-mode-map [(shift tab)] 'org-shifttab)
(define-key org-mode-map [backtab] 'org-shifttab)
@@ -19079,6 +19791,8 @@ BEG and END default to the buffer boundaries."
(org-defkey org-mode-map [(meta up)] 'org-metaup)
(org-defkey org-mode-map [(meta down)] 'org-metadown)
+(org-defkey org-mode-map [(control meta shift right)] 'org-increase-number-at-point)
+(org-defkey org-mode-map [(control meta shift left)] 'org-decrease-number-at-point)
(org-defkey org-mode-map [(meta shift left)] 'org-shiftmetaleft)
(org-defkey org-mode-map [(meta shift right)] 'org-shiftmetaright)
(org-defkey org-mode-map [(meta shift up)] 'org-shiftmetaup)
@@ -19096,17 +19810,14 @@ BEG and END default to the buffer boundaries."
;; Babel keys
(define-key org-mode-map org-babel-key-prefix org-babel-map)
-(mapc (lambda (pair)
- (define-key org-babel-map (car pair) (cdr pair)))
- org-babel-key-bindings)
+(dolist (pair org-babel-key-bindings)
+ (define-key org-babel-map (car pair) (cdr pair)))
;;; Extra keys for tty access.
;; We only set them when really needed because otherwise the
;; menus don't show the simple keys
-(when (or org-use-extra-keys
- (featurep 'xemacs) ;; because XEmacs supports multi-device stuff
- (not window-system))
+(when (or org-use-extra-keys (not window-system))
(org-defkey org-mode-map "\C-c\C-xc" 'org-table-copy-down)
(org-defkey org-mode-map "\C-c\C-xM" 'org-insert-todo-heading)
(org-defkey org-mode-map "\C-c\C-xm" 'org-meta-return)
@@ -19138,7 +19849,7 @@ BEG and END default to the buffer boundaries."
;; All the other keys
-(org-defkey org-mode-map "\C-c\C-a" 'show-all) ; in case allout messed up.
+(org-defkey org-mode-map "\C-c\C-a" 'outline-show-all) ; in case allout messed up.
(org-defkey org-mode-map "\C-c\C-r" 'org-reveal)
(if (boundp 'narrow-map)
(org-defkey narrow-map "s" 'org-narrow-to-subtree)
@@ -19185,6 +19896,7 @@ BEG and END default to the buffer boundaries."
(org-defkey org-mode-map "\C-c\C-x\C-n" 'org-next-link)
(org-defkey org-mode-map "\C-c\C-x\C-p" 'org-previous-link)
(org-defkey org-mode-map "\C-c\C-l" 'org-insert-link)
+(org-defkey org-mode-map "\C-c\M-l" 'org-insert-last-stored-link)
(org-defkey org-mode-map "\C-c\C-\M-l" 'org-insert-all-links)
(org-defkey org-mode-map "\C-c\C-o" 'org-open-at-point)
(org-defkey org-mode-map "\C-c%" 'org-mark-ring-push)
@@ -19209,8 +19921,10 @@ BEG and END default to the buffer boundaries."
(org-defkey org-mode-map "\C-c\C-k" 'org-kill-note-or-show-branches)
(org-defkey org-mode-map "\C-c#" 'org-update-statistics-cookies)
(org-defkey org-mode-map [remap open-line] 'org-open-line)
+(org-defkey org-mode-map [remap comment-dwim] 'org-comment-dwim)
(org-defkey org-mode-map [remap forward-paragraph] 'org-forward-paragraph)
(org-defkey org-mode-map [remap backward-paragraph] 'org-backward-paragraph)
+(org-defkey org-mode-map "\M-^" 'org-delete-indentation)
(org-defkey org-mode-map "\C-m" 'org-return)
(org-defkey org-mode-map "\C-j" 'org-return-indent)
(org-defkey org-mode-map "\C-c?" 'org-table-field-info)
@@ -19219,6 +19933,8 @@ BEG and END default to the buffer boundaries."
(org-defkey org-mode-map "\C-c=" 'org-table-eval-formula)
(org-defkey org-mode-map "\C-c'" 'org-edit-special)
(org-defkey org-mode-map "\C-c`" 'org-table-edit-field)
+(org-defkey org-mode-map "\C-c\"a" 'orgtbl-ascii-plot)
+(org-defkey org-mode-map "\C-c\"g" 'org-plot/gnuplot)
(org-defkey org-mode-map "\C-c|" 'org-table-create-or-convert-from-region)
(org-defkey org-mode-map [(control ?#)] 'org-table-rotate-recalc-marks)
(org-defkey org-mode-map "\C-c~" 'org-table-create-with-table.el)
@@ -19226,7 +19942,7 @@ BEG and END default to the buffer boundaries."
(org-defkey org-mode-map "\C-c}" 'org-table-toggle-coordinate-overlays)
(org-defkey org-mode-map "\C-c{" 'org-table-toggle-formula-debugger)
(org-defkey org-mode-map "\C-c\C-e" 'org-export-dispatch)
-(org-defkey org-mode-map "\C-c:" 'org-toggle-fixed-width-section)
+(org-defkey org-mode-map "\C-c:" 'org-toggle-fixed-width)
(org-defkey org-mode-map "\C-c\C-x\C-f" 'org-emphasize)
(org-defkey org-mode-map "\C-c\C-xf" 'org-footnote-action)
(org-defkey org-mode-map "\C-c\C-x\C-mg" 'org-mobile-pull)
@@ -19250,7 +19966,7 @@ BEG and END default to the buffer boundaries."
(org-defkey org-mode-map "\C-c\C-x\C-d" 'org-clock-display)
(org-defkey org-mode-map "\C-c\C-x\C-r" 'org-clock-report)
(org-defkey org-mode-map "\C-c\C-x\C-u" 'org-dblock-update)
-(org-defkey org-mode-map "\C-c\C-x\C-l" 'org-preview-latex-fragment)
+(org-defkey org-mode-map "\C-c\C-x\C-l" 'org-toggle-latex-fragment)
(org-defkey org-mode-map "\C-c\C-x\C-v" 'org-toggle-inline-images)
(org-defkey org-mode-map "\C-c\C-x\C-\M-v" 'org-redisplay-inline-images)
(org-defkey org-mode-map "\C-c\C-x\\" 'org-toggle-pretty-entities)
@@ -19260,9 +19976,8 @@ BEG and END default to the buffer boundaries."
(org-defkey org-mode-map "\C-c\C-xe" 'org-set-effort)
(org-defkey org-mode-map "\C-c\C-xE" 'org-inc-effort)
(org-defkey org-mode-map "\C-c\C-xo" 'org-toggle-ordered-property)
-(org-defkey org-mode-map "\C-c\C-xi" 'org-insert-columns-dblock)
+(org-defkey org-mode-map "\C-c\C-xi" 'org-columns-insert-dblock)
(org-defkey org-mode-map [(control ?c) (control ?x) ?\;] 'org-timer-set-timer)
-(org-defkey org-mode-map [(control ?c) (control ?x) ?\:] 'org-timer-cancel-timer)
(org-defkey org-mode-map "\C-c\C-x." 'org-timer)
(org-defkey org-mode-map "\C-c\C-x-" 'org-timer-item)
@@ -19280,15 +19995,11 @@ BEG and END default to the buffer boundaries."
(define-key org-mode-map "\C-c\C-x[" 'org-reftex-citation)
-(when (featurep 'xemacs)
- (org-defkey org-mode-map 'button3 'popup-mode-menu))
-
-
(defconst org-speed-commands-default
'(
("Outline Navigation")
- ("n" . (org-speed-move-safe 'outline-next-visible-heading))
- ("p" . (org-speed-move-safe 'outline-previous-visible-heading))
+ ("n" . (org-speed-move-safe 'org-next-visible-heading))
+ ("p" . (org-speed-move-safe 'org-previous-visible-heading))
("f" . (org-speed-move-safe 'org-forward-heading-same-level))
("b" . (org-speed-move-safe 'org-backward-heading-same-level))
("F" . org-next-block)
@@ -19303,8 +20014,8 @@ BEG and END default to the buffer boundaries."
("s" . org-narrow-to-subtree)
("=" . org-columns)
("Outline Structure Editing")
- ("U" . org-shiftmetaup)
- ("D" . org-shiftmetadown)
+ ("U" . org-metaup)
+ ("D" . org-metadown)
("r" . org-metaright)
("l" . org-metaleft)
("R" . org-shiftmetaright)
@@ -19364,10 +20075,10 @@ BEG and END default to the buffer boundaries."
(user-error "Speed commands are not activated, customize `org-use-speed-commands'")
(with-output-to-temp-buffer "*Help*"
(princ "User-defined Speed commands\n===========================\n")
- (mapc 'org-print-speed-command org-speed-commands-user)
+ (mapc #'org-print-speed-command org-speed-commands-user)
(princ "\n")
(princ "Built-in Speed commands\n=======================\n")
- (mapc 'org-print-speed-command org-speed-commands-default))
+ (mapc #'org-print-speed-command org-speed-commands-default))
(with-current-buffer "*Help*"
(setq truncate-lines t))))
@@ -19386,9 +20097,6 @@ If not, return to the original position and throw an error."
(defvar org-table-auto-blank-field) ; defined in org-table.el
(defvar org-speed-command nil)
-(define-obsolete-function-alias
- 'org-speed-command-default-hook 'org-speed-command-activate "24.3")
-
(defun org-speed-command-activate (keys)
"Hook for activating single-letter speed commands.
`org-speed-commands-default' specifies a minimal command set.
@@ -19399,9 +20107,6 @@ Use `org-speed-commands-user' for further customization."
(cdr (assoc keys (append org-speed-commands-user
org-speed-commands-default)))))
-(define-obsolete-function-alias
- 'org-babel-speed-command-hook 'org-babel-speed-command-activate "24.3")
-
(defun org-babel-speed-command-activate (keys)
"Hook for activating single-letter code block commands."
(when (and (bolp) (looking-at org-babel-src-block-regexp))
@@ -19434,9 +20139,11 @@ overwritten, and the table is not marked as requiring realignment."
(org-check-before-invisible-edit 'insert)
(cond
((and org-use-speed-commands
- (setq org-speed-command
- (run-hook-with-args-until-success
- 'org-speed-command-hook (this-command-keys))))
+ (let ((kv (this-command-keys-vector)))
+ (setq org-speed-command
+ (run-hook-with-args-until-success
+ 'org-speed-command-hook
+ (make-string 1 (aref kv (1- (length kv))))))))
(cond
((commandp org-speed-command)
(setq this-command org-speed-command)
@@ -19448,94 +20155,98 @@ overwritten, and the table is not marked as requiring realignment."
(t (let (org-use-speed-commands)
(call-interactively 'org-self-insert-command)))))
((and
- (org-table-p)
+ (org-at-table-p)
(progn
- ;; check if we blank the field, and if that triggers align
+ ;; Check if we blank the field, and if that triggers align.
(and (featurep 'org-table) org-table-auto-blank-field
- (member last-command
- '(org-cycle org-return org-shifttab org-ctrl-c-ctrl-c yas/expand))
- (if (or (equal (char-after) ?\ ) (looking-at "[^|\n]* |"))
- ;; got extra space, this field does not determine column width
+ (memq last-command
+ '(org-cycle org-return org-shifttab org-ctrl-c-ctrl-c))
+ (if (or (eq (char-after) ?\s) (looking-at "[^|\n]* |"))
+ ;; Got extra space, this field does not determine
+ ;; column width.
(let (org-table-may-need-update) (org-table-blank-field))
- ;; no extra space, this field may determine column width
+ ;; No extra space, this field may determine column
+ ;; width.
(org-table-blank-field)))
t)
(eq N 1)
- (looking-at "[^|\n]* |"))
- (let (org-table-may-need-update)
- (goto-char (1- (match-end 0)))
- (backward-delete-char 1)
- (goto-char (match-beginning 0))
- (self-insert-command N)))
+ (looking-at "[^|\n]* \\( \\)|"))
+ ;; There is room for insertion without re-aligning the table.
+ (delete-region (match-beginning 1) (match-end 1))
+ (self-insert-command N))
(t
(setq org-table-may-need-update t)
(self-insert-command N)
(org-fix-tags-on-the-fly)
- (if org-self-insert-cluster-for-undo
- (if (not (eq last-command 'org-self-insert-command))
+ (when org-self-insert-cluster-for-undo
+ (if (not (eq last-command 'org-self-insert-command))
+ (setq org-self-insert-command-undo-counter 1)
+ (if (>= org-self-insert-command-undo-counter 20)
(setq org-self-insert-command-undo-counter 1)
- (if (>= org-self-insert-command-undo-counter 20)
- (setq org-self-insert-command-undo-counter 1)
- (and (> org-self-insert-command-undo-counter 0)
- buffer-undo-list (listp buffer-undo-list)
- (not (cadr buffer-undo-list)) ; remove nil entry
- (setcdr buffer-undo-list (cddr buffer-undo-list)))
- (setq org-self-insert-command-undo-counter
- (1+ org-self-insert-command-undo-counter))))))))
+ (and (> org-self-insert-command-undo-counter 0)
+ buffer-undo-list (listp buffer-undo-list)
+ (not (cadr buffer-undo-list)) ; remove nil entry
+ (setcdr buffer-undo-list (cddr buffer-undo-list)))
+ (setq org-self-insert-command-undo-counter
+ (1+ org-self-insert-command-undo-counter))))))))
(defun org-check-before-invisible-edit (kind)
"Check is editing if kind KIND would be dangerous with invisible text around.
The detailed reaction depends on the user option `org-catch-invisible-edits'."
;; First, try to get out of here as quickly as possible, to reduce overhead
- (if (and org-catch-invisible-edits
- (or (not (boundp 'visible-mode)) (not visible-mode))
- (or (get-char-property (point) 'invisible)
- (get-char-property (max (point-min) (1- (point))) 'invisible)))
- ;; OK, we need to take a closer look
- (let* ((invisible-at-point (get-char-property (point) 'invisible))
- (invisible-before-point (if (bobp) nil (get-char-property
- (1- (point)) 'invisible)))
- (border-and-ok-direction
- (or
- ;; Check if we are acting predictably before invisible text
- (and invisible-at-point (not invisible-before-point)
- (memq kind '(insert delete-backward)))
- ;; Check if we are acting predictably after invisible text
- ;; This works not well, and I have turned it off. It seems
- ;; better to always show and stop after invisible text.
- ;; (and (not invisible-at-point) invisible-before-point
- ;; (memq kind '(insert delete)))
- )))
- (when (or (memq invisible-at-point '(outline org-hide-block t))
- (memq invisible-before-point '(outline org-hide-block t)))
- (if (eq org-catch-invisible-edits 'error)
- (user-error "Editing in invisible areas is prohibited, make them visible first"))
- (if (and org-custom-properties-overlays
- (y-or-n-p "Display invisible properties in this buffer? "))
- (org-toggle-custom-properties-visibility)
- ;; Make the area visible
- (save-excursion
- (if invisible-before-point
- (goto-char (previous-single-char-property-change
- (point) 'invisible)))
- (show-subtree))
- (cond
- ((eq org-catch-invisible-edits 'show)
- ;; That's it, we do the edit after showing
- (message
- "Unfolding invisible region around point before editing")
- (sit-for 1))
- ((and (eq org-catch-invisible-edits 'smart)
- border-and-ok-direction)
- (message "Unfolding invisible region around point before editing"))
- (t
- ;; Don't do the edit, make the user repeat it in full visibility
- (user-error "Edit in invisible region aborted, repeat to confirm with text visible"))))))))
+ (when (and org-catch-invisible-edits
+ (or (not (boundp 'visible-mode)) (not visible-mode))
+ (or (get-char-property (point) 'invisible)
+ (get-char-property (max (point-min) (1- (point))) 'invisible)))
+ ;; OK, we need to take a closer look
+ (let* ((invisible-at-point (get-char-property (point) 'invisible))
+ (invisible-before-point (unless (bobp) (get-char-property
+ (1- (point)) 'invisible)))
+ (border-and-ok-direction
+ (or
+ ;; Check if we are acting predictably before invisible text
+ (and invisible-at-point (not invisible-before-point)
+ (memq kind '(insert delete-backward)))
+ ;; Check if we are acting predictably after invisible text
+ ;; This works not well, and I have turned it off. It seems
+ ;; better to always show and stop after invisible text.
+ ;; (and (not invisible-at-point) invisible-before-point
+ ;; (memq kind '(insert delete)))
+ )))
+ (when (or (memq invisible-at-point '(outline org-hide-block t))
+ (memq invisible-before-point '(outline org-hide-block t)))
+ (when (eq org-catch-invisible-edits 'error)
+ (user-error "Editing in invisible areas is prohibited, make them visible first"))
+ (if (and org-custom-properties-overlays
+ (y-or-n-p "Display invisible properties in this buffer? "))
+ (org-toggle-custom-properties-visibility)
+ ;; Make the area visible
+ (save-excursion
+ (when invisible-before-point
+ (goto-char (previous-single-char-property-change
+ (point) 'invisible)))
+ (outline-show-subtree))
+ (cond
+ ((eq org-catch-invisible-edits 'show)
+ ;; That's it, we do the edit after showing
+ (message
+ "Unfolding invisible region around point before editing")
+ (sit-for 1))
+ ((and (eq org-catch-invisible-edits 'smart)
+ border-and-ok-direction)
+ (message "Unfolding invisible region around point before editing"))
+ (t
+ ;; Don't do the edit, make the user repeat it in full visibility
+ (user-error "Edit in invisible region aborted, repeat to confirm with text visible"))))))))
(defun org-fix-tags-on-the-fly ()
- (when (and (equal (char-after (point-at-bol)) ?*)
+ "Align tags in headline at point.
+Unlike to `org-set-tags', it ignores region and sorting."
+ (when (and (eq (char-after (line-beginning-position)) ?*) ;short-circuit
(org-at-heading-p))
- (org-align-tags-here org-tags-column)))
+ (let ((org-ignore-region t)
+ (org-tags-sort-function nil))
+ (org-set-tags nil t))))
(defun org-delete-backward-char (N)
"Like `delete-backward-char', insert whitespace at field end in tables.
@@ -19546,7 +20257,7 @@ because, in this case the deletion might narrow the column."
(interactive "p")
(save-match-data
(org-check-before-invisible-edit 'delete-backward)
- (if (and (org-table-p)
+ (if (and (org-at-table-p)
(eq N 1)
(string-match "|" (buffer-substring (point-at-bol) (point)))
(looking-at ".*?|"))
@@ -19554,14 +20265,13 @@ because, in this case the deletion might narrow the column."
(noalign (looking-at "[^|\n\r]* |"))
(c org-table-may-need-update))
(backward-delete-char N)
- (if (not overwrite-mode)
- (progn
- (skip-chars-forward "^|")
- (insert " ")
- (goto-char (1- pos))))
+ (unless overwrite-mode
+ (skip-chars-forward "^|")
+ (insert " ")
+ (goto-char (1- pos)))
;; noalign: if there were two spaces at the end, this field
;; does not determine the width of the column.
- (if noalign (setq org-table-may-need-update c)))
+ (when noalign (setq org-table-may-need-update c)))
(backward-delete-char N)
(org-fix-tags-on-the-fly))))
@@ -19574,7 +20284,7 @@ because, in this case the deletion might narrow the column."
(interactive "p")
(save-match-data
(org-check-before-invisible-edit 'delete)
- (if (and (org-table-p)
+ (if (and (org-at-table-p)
(not (bolp))
(not (= (char-after) ?|))
(eq N 1))
@@ -19587,12 +20297,12 @@ because, in this case the deletion might narrow the column."
(goto-char pos)
;; noalign: if there were two spaces at the end, this field
;; does not determine the width of the column.
- (if noalign (setq org-table-may-need-update c)))
+ (when noalign (setq org-table-may-need-update c)))
(delete-char N))
(delete-char N)
(org-fix-tags-on-the-fly))))
-;; Make `delete-selection-mode' work with org-mode and orgtbl-mode
+;; Make `delete-selection-mode' work with Org mode and Orgtbl mode
(put 'org-self-insert-command 'delete-selection
(lambda ()
(not (run-hook-with-args-until-success
@@ -19611,7 +20321,7 @@ because, in this case the deletion might narrow the column."
(put 'org-delete-char 'flyspell-delayed t)
(put 'org-delete-backward-char 'flyspell-delayed t)
-;; Make pabbrev-mode expand after org-mode commands
+;; Make pabbrev-mode expand after Org mode commands
(put 'org-self-insert-command 'pabbrev-expand-after-command t)
(put 'orgtbl-self-insert-command 'pabbrev-expand-after-command t)
@@ -19621,9 +20331,7 @@ COMMANDS is a list of alternating OLDDEF NEWDEF command names."
(let (new old)
(while commands
(setq old (pop commands) new (pop commands))
- (if (fboundp 'command-remapping)
- (org-defkey map (vector 'remap old) new)
- (substitute-key-definition old new map global-map)))))
+ (org-defkey map (vector 'remap old) new))))
(defun org-transpose-words ()
"Transpose words for Org.
@@ -19765,7 +20473,7 @@ See `org-ctrl-c-ctrl-c-hook' for more information.")
(defun org-shiftselect-error ()
"Throw an error because Shift-Cursor command was applied in wrong context."
(if (and (boundp 'shift-select-mode) shift-select-mode)
- (user-error "To use shift-selection with Org-mode, customize `org-support-shift-select'")
+ (user-error "To use shift-selection with Org mode, customize `org-support-shift-select'")
(user-error "This command works only in special context like headlines or timestamps")))
(defun org-call-for-shift-select (cmd)
@@ -19820,32 +20528,30 @@ individual commands for more information."
(call-interactively 'org-indent-item-tree))
(t (org-modifier-cursor-error))))
-(defun org-shiftmetaup (&optional arg)
- "Move subtree up or kill table row.
-Calls `org-move-subtree-up' or `org-table-kill-row' or
-`org-move-item-up' or `org-timestamp-up', depending on context.
-See the individual commands for more information."
+(defun org-shiftmetaup (&optional _arg)
+ "Drag the line at point up.
+In a table, kill the current row.
+On a clock timestamp, update the value of the timestamp like `S-'
+but also adjust the previous clocked item in the clock history.
+Everywhere else, drag the line at point up."
(interactive "P")
(cond
((run-hook-with-args-until-success 'org-shiftmetaup-hook))
((org-at-table-p) (call-interactively 'org-table-kill-row))
- ((org-at-heading-p) (call-interactively 'org-move-subtree-up))
- ((org-at-item-p) (call-interactively 'org-move-item-up))
((org-at-clock-log-p) (let ((org-clock-adjust-closest t))
(call-interactively 'org-timestamp-up)))
(t (call-interactively 'org-drag-line-backward))))
-(defun org-shiftmetadown (&optional arg)
- "Move subtree down or insert table row.
-Calls `org-move-subtree-down' or `org-table-insert-row' or
-`org-move-item-down' or `org-timestamp-up', depending on context.
-See the individual commands for more information."
+(defun org-shiftmetadown (&optional _arg)
+ "Drag the line at point down.
+In a table, insert an empty row at the current line.
+On a clock timestamp, update the value of the timestamp like `S-'
+but also adjust the previous clocked item in the clock history.
+Everywhere else, drag the line at point down."
(interactive "P")
(cond
((run-hook-with-args-until-success 'org-shiftmetadown-hook))
((org-at-table-p) (call-interactively 'org-table-insert-row))
- ((org-at-heading-p) (call-interactively 'org-move-subtree-down))
- ((org-at-item-p) (call-interactively 'org-move-item-down))
((org-at-clock-log-p) (let ((org-clock-adjust-closest t))
(call-interactively 'org-timestamp-down)))
(t (call-interactively 'org-drag-line-forward))))
@@ -19854,11 +20560,16 @@ See the individual commands for more information."
(user-error
"Hidden subtree, open with TAB or use subtree command M-S-/"))
-(defun org-metaleft (&optional arg)
- "Promote heading or move table column to left.
-Calls `org-do-promote' or `org-table-move-column', depending on context.
-With no specific context, calls the Emacs default `backward-word'.
-See the individual commands for more information."
+(defun org-metaleft (&optional _arg)
+ "Promote heading, list item at point or move table column left.
+
+Calls `org-do-promote', `org-outdent-item' or `org-table-move-column',
+depending on context. With no specific context, calls the Emacs
+default `backward-word'. See the individual commands for more
+information.
+
+This function runs the hook `org-metaleft-hook' as a first step,
+and returns at first non-nil value."
(interactive "P")
(cond
((run-hook-with-args-until-success 'org-metaleft-hook))
@@ -19883,11 +20594,18 @@ See the individual commands for more information."
(call-interactively 'org-outdent-item))
(t (call-interactively 'backward-word))))
-(defun org-metaright (&optional arg)
- "Demote a subtree, a list item or move table column to right.
+(defun org-metaright (&optional _arg)
+ "Demote heading, list item at point or move table column right.
+
In front of a drawer or a block keyword, indent it correctly.
+
+Calls `org-do-demote', `org-indent-item', `org-table-move-column',
+`org-indent-drawer' or `org-indent-block' depending on context.
With no specific context, calls the Emacs default `forward-word'.
-See the individual commands for more information."
+See the individual commands for more information.
+
+This function runs the hook `org-metaright-hook' as a first step,
+and returns at first non-nil value."
(interactive "P")
(cond
((run-hook-with-args-until-success 'org-metaright-hook))
@@ -19937,11 +20655,11 @@ this function returns t, nil otherwise."
(goto-char (point-at-eol))
(setq end (max end (point)))
(while (re-search-forward re end t)
- (if (get-char-property (match-beginning 0) 'invisible)
- (throw 'exit t))))
+ (when (get-char-property (match-beginning 0) 'invisible)
+ (throw 'exit t))))
nil))))
-(defun org-metaup (&optional arg)
+(defun org-metaup (&optional _arg)
"Move subtree up or move table row up.
Calls `org-move-subtree-up' or `org-table-move-row' or
`org-move-item-up', depending on context. See the individual commands
@@ -19963,7 +20681,7 @@ for more information."
((org-at-item-p) (call-interactively 'org-move-item-up))
(t (org-drag-element-backward))))
-(defun org-metadown (&optional arg)
+(defun org-metadown (&optional _arg)
"Move subtree down or move table row down.
Calls `org-move-subtree-down' or `org-table-move-row' or
`org-move-item-down', depending on context. See the individual
@@ -20149,6 +20867,32 @@ Optional argument N tells to change by that many units."
(org-clock-timestamps-down n))
(user-error "Not at a clock log")))
+(defun org-increase-number-at-point (&optional inc)
+ "Increment the number at point.
+With an optional prefix numeric argument INC, increment using
+this numeric value."
+ (interactive "p")
+ (if (not (number-at-point))
+ (user-error "Not on a number")
+ (unless inc (setq inc 1))
+ (let ((pos (point))
+ (beg (skip-chars-backward "-+^/*0-9eE."))
+ (end (skip-chars-forward "-+^/*0-9eE^.")) nap)
+ (setq nap (buffer-substring-no-properties
+ (+ pos beg) (+ pos beg end)))
+ (delete-region (+ pos beg) (+ pos beg end))
+ (insert (calc-eval (concat (number-to-string inc) "+" nap))))
+ (when (org-at-table-p)
+ (org-table-align)
+ (org-table-end-of-field 1))))
+
+(defun org-decrease-number-at-point (&optional inc)
+ "Decrement the number at point.
+With an optional prefix numeric argument INC, decrement using
+this numeric value."
+ (interactive "p")
+ (org-increase-number-at-point (- (or inc 1))))
+
(defun org-ctrl-c-ret ()
"Call `org-table-hline-and-move' or `org-insert-heading' dep. on context."
(interactive)
@@ -20183,19 +20927,19 @@ Optional argument N tells to change by that many units."
(defun org-copy-special ()
"Copy region in table or copy current subtree.
-Calls `org-table-copy' or `org-copy-subtree', depending on context.
-See the individual commands for more information."
+Calls `org-table-copy-region' or `org-copy-subtree', depending on
+context. See the individual commands for more information."
(interactive)
(call-interactively
- (if (org-at-table-p) 'org-table-copy-region 'org-copy-subtree)))
+ (if (org-at-table-p) #'org-table-copy-region #'org-copy-subtree)))
(defun org-cut-special ()
"Cut region in table or cut current subtree.
-Calls `org-table-copy' or `org-cut-subtree', depending on context.
-See the individual commands for more information."
+Calls `org-table-cut-region' or `org-cut-subtree', depending on
+context. See the individual commands for more information."
(interactive)
(call-interactively
- (if (org-at-table-p) 'org-table-cut-region 'org-cut-subtree)))
+ (if (org-at-table-p) #'org-table-cut-region #'org-cut-subtree)))
(defun org-paste-special (arg)
"Paste rectangular region into table, or past subtree relative to level.
@@ -20206,57 +20950,65 @@ See the individual commands for more information."
(org-table-paste-rectangle)
(org-paste-subtree arg)))
-(defsubst org-in-fixed-width-region-p ()
- "Is point in a fixed-width region?"
- (save-match-data
- (eq 'fixed-width (org-element-type (org-element-at-point)))))
-
(defun org-edit-special (&optional arg)
"Call a special editor for the element at point.
When at a table, call the formula editor with `org-table-edit-formulas'.
When in a source code block, call `org-edit-src-code'.
When in a fixed-width region, call `org-edit-fixed-width-region'.
+When in an export block, call `org-edit-export-block'.
When at an #+INCLUDE keyword, visit the included file.
+When at a footnote reference, call `org-edit-footnote-reference'
On a link, call `ffap' to visit the link at point.
Otherwise, return a user error."
(interactive "P")
(let ((element (org-element-at-point)))
- (assert (not buffer-read-only) nil
- "Buffer is read-only: %s" (buffer-name))
- (case (org-element-type element)
- (src-block
+ (barf-if-buffer-read-only)
+ (pcase (org-element-type element)
+ (`src-block
(if (not arg) (org-edit-src-code)
- (let* ((info (org-babel-get-src-block-info))
- (lang (nth 0 info))
- (params (nth 2 info))
- (session (cdr (assq :session params))))
- (if (not session) (org-edit-src-code)
- ;; At a src-block with a session and function called with
- ;; an ARG: switch to the buffer related to the inferior
- ;; process.
- (switch-to-buffer
+ (let* ((info (org-babel-get-src-block-info))
+ (lang (nth 0 info))
+ (params (nth 2 info))
+ (session (cdr (assq :session params))))
+ (if (not session) (org-edit-src-code)
+ ;; At a src-block with a session and function called with
+ ;; an ARG: switch to the buffer related to the inferior
+ ;; process.
+ (switch-to-buffer
(funcall (intern (concat "org-babel-prep-session:" lang))
session params))))))
- (keyword
+ (`keyword
(if (member (org-element-property :key element) '("INCLUDE" "SETUPFILE"))
- (find-file
- (org-remove-double-quotes
- (car (org-split-string (org-element-property :value element)))))
+ (org-open-link-from-string
+ (format "[[%s]]"
+ (expand-file-name
+ (let ((value (org-element-property :value element)))
+ (cond ((not (org-string-nw-p value))
+ (user-error "No file to edit"))
+ ((string-match "\\`\"\\(.*?\\)\"" value)
+ (match-string 1 value))
+ ((string-match "\\`[^ \t\"]\\S-*" value)
+ (match-string 0 value))
+ (t (user-error "No valid file specified")))))))
(user-error "No special environment to edit here")))
- (table
+ (`table
(if (eq (org-element-property :type element) 'table.el)
- (org-edit-src-code)
+ (org-edit-table.el)
(call-interactively 'org-table-edit-formulas)))
;; Only Org tables contain `table-row' type elements.
- (table-row (call-interactively 'org-table-edit-formulas))
- ((example-block export-block) (org-edit-src-code))
- (fixed-width (org-edit-fixed-width-region))
- (otherwise
- ;; No notable element at point. Though, we may be at a link,
- ;; which is an object. Thus, scan deeper.
- (if (eq (org-element-type (org-element-context element)) 'link)
- (call-interactively 'ffap)
- (user-error "No special environment to edit here"))))))
+ (`table-row (call-interactively 'org-table-edit-formulas))
+ (`example-block (org-edit-src-code))
+ (`export-block (org-edit-export-block))
+ (`fixed-width (org-edit-fixed-width-region))
+ (_
+ ;; No notable element at point. Though, we may be at a link or
+ ;; a footnote reference, which are objects. Thus, scan deeper.
+ (let ((context (org-element-context element)))
+ (pcase (org-element-type context)
+ (`footnote-reference (org-edit-footnote-reference))
+ (`inline-src-block (org-edit-inline-src-code))
+ (`link (call-interactively #'ffap))
+ (_ (user-error "No special environment to edit here"))))))))
(defvar org-table-coordinate-overlays) ; defined in org-table.el
(defun org-ctrl-c-ctrl-c (&optional arg)
@@ -20305,240 +21057,314 @@ This command does many different things, depending on context:
inhibited by setting `org-babel-no-eval-on-ctrl-c-ctrl-c'."
(interactive "P")
(cond
- ((or (and (boundp 'org-clock-overlays) org-clock-overlays)
- org-occur-highlights
- org-latex-fragment-image-overlays)
- (and (boundp 'org-clock-overlays) (org-clock-remove-overlays))
+ ((or (bound-and-true-p org-clock-overlays) org-occur-highlights)
+ (when (boundp 'org-clock-overlays) (org-clock-remove-overlays))
(org-remove-occur-highlights)
- (org-remove-latex-fragment-image-overlays)
(message "Temporary highlights/overlays removed from current buffer"))
- ((and (local-variable-p 'org-finish-function (current-buffer))
+ ((and (local-variable-p 'org-finish-function)
(fboundp org-finish-function))
(funcall org-finish-function))
+ ((org-babel-hash-at-point))
((run-hook-with-args-until-success 'org-ctrl-c-ctrl-c-hook))
(t
- (let* ((context (org-element-context)) (type (org-element-type context)))
- ;; Test if point is within a blank line.
- (if (save-excursion (beginning-of-line) (looking-at "[ \t]*$"))
- (or (run-hook-with-args-until-success 'org-ctrl-c-ctrl-c-final-hook)
- (user-error "C-c C-c can do nothing useful at this location"))
- (case type
- ;; When at a link, act according to the parent instead.
- (link (setq context (org-element-property :parent context))
- (setq type (org-element-type context)))
- ;; Unsupported object types: refer to the first supported
- ;; element or object containing it.
- ((bold code entity export-snippet inline-babel-call inline-src-block
- italic latex-fragment line-break macro strike-through subscript
- superscript underline verbatim)
- (while (and (setq context (org-element-property :parent context))
- (not (memq (setq type (org-element-type context))
- '(radio-target paragraph verse-block
- table-cell)))))))
- ;; For convenience: at the first line of a paragraph on the
- ;; same line as an item, apply function on that item instead.
- (when (eq type 'paragraph)
- (let ((parent (org-element-property :parent context)))
- (when (and (eq (org-element-type parent) 'item)
- (= (point-at-bol) (org-element-property :begin parent)))
- (setq context parent type 'item))))
- ;; Act according to type of element or object at point.
- (case type
- (clock (org-clock-update-time-maybe))
- (dynamic-block
- (save-excursion
- (goto-char (org-element-property :post-affiliated context))
- (org-update-dblock)))
- (footnote-definition
+ (let* ((context
+ (org-element-lineage
+ (org-element-context)
+ ;; Limit to supported contexts.
+ '(babel-call clock dynamic-block footnote-definition
+ footnote-reference inline-babel-call inline-src-block
+ inlinetask item keyword node-property paragraph
+ plain-list property-drawer radio-target src-block
+ statistics-cookie table table-cell table-row
+ timestamp)
+ t))
+ (type (org-element-type context)))
+ ;; For convenience: at the first line of a paragraph on the same
+ ;; line as an item, apply function on that item instead.
+ (when (eq type 'paragraph)
+ (let ((parent (org-element-property :parent context)))
+ (when (and (eq (org-element-type parent) 'item)
+ (= (line-beginning-position)
+ (org-element-property :begin parent)))
+ (setq context parent)
+ (setq type 'item))))
+ ;; Act according to type of element or object at point.
+ ;;
+ ;; Do nothing on a blank line, except if it is contained in
+ ;; a src block. Hence, we first check if point is in such
+ ;; a block and then if it is at a blank line.
+ (pcase type
+ ((or `inline-src-block `src-block)
+ (unless org-babel-no-eval-on-ctrl-c-ctrl-c
+ (org-babel-eval-wipe-error-buffer)
+ (org-babel-execute-src-block
+ current-prefix-arg (org-babel-get-src-block-info nil context))))
+ ((guard (org-match-line "[ \t]*$"))
+ (or (run-hook-with-args-until-success 'org-ctrl-c-ctrl-c-final-hook)
+ (user-error
+ (substitute-command-keys
+ "`\\[org-ctrl-c-ctrl-c]' can do nothing useful here"))))
+ ((or `babel-call `inline-babel-call)
+ (let ((info (org-babel-lob-get-info context)))
+ (when info (org-babel-execute-src-block nil info))))
+ (`clock (org-clock-update-time-maybe))
+ (`dynamic-block
+ (save-excursion
(goto-char (org-element-property :post-affiliated context))
- (call-interactively 'org-footnote-action))
- (footnote-reference (call-interactively 'org-footnote-action))
- ((headline inlinetask)
- (save-excursion (goto-char (org-element-property :begin context))
- (call-interactively 'org-set-tags)))
- (item
- ;; At an item: a double C-u set checkbox to "[-]"
- ;; unconditionally, whereas a single one will toggle its
- ;; presence. Without a universal argument, if the item
- ;; has a checkbox, toggle it. Otherwise repair the list.
- (let* ((box (org-element-property :checkbox context))
- (struct (org-element-property :structure context))
- (old-struct (copy-tree struct))
- (parents (org-list-parents-alist struct))
- (prevs (org-list-prevs-alist struct))
- (orderedp (org-not-nil (org-entry-get nil "ORDERED"))))
- (org-list-set-checkbox
- (org-element-property :begin context) struct
- (cond ((equal arg '(16)) "[-]")
- ((and (not box) (equal arg '(4))) "[ ]")
- ((or (not box) (equal arg '(4))) nil)
- ((eq box 'on) "[ ]")
- (t "[X]")))
- ;; Mimic `org-list-write-struct' but with grabbing
- ;; a return value from `org-list-struct-fix-box'.
- (org-list-struct-fix-ind struct parents 2)
- (org-list-struct-fix-item-end struct)
- (org-list-struct-fix-bul struct prevs)
- (org-list-struct-fix-ind struct parents)
- (let ((block-item
- (org-list-struct-fix-box struct parents prevs orderedp)))
- (if (and box (equal struct old-struct))
- (if (equal arg '(16))
- (message "Checkboxes already reset")
- (user-error "Cannot toggle this checkbox: %s"
- (if (eq box 'on)
- "all subitems checked"
- "unchecked subitems")))
- (org-list-struct-apply-struct struct old-struct)
- (org-update-checkbox-count-maybe))
- (when block-item
- (message "Checkboxes were removed due to empty box at line %d"
- (org-current-line block-item))))))
- (keyword
- (let ((org-inhibit-startup-visibility-stuff t)
- (org-startup-align-all-tables nil))
- (when (boundp 'org-table-coordinate-overlays)
- (mapc 'delete-overlay org-table-coordinate-overlays)
- (setq org-table-coordinate-overlays nil))
- (org-save-outline-visibility 'use-markers (org-mode-restart)))
- (message "Local setup has been refreshed"))
- (plain-list
- ;; At a plain list, with a double C-u argument, set
- ;; checkboxes of each item to "[-]", whereas a single one
- ;; will toggle their presence according to the state of the
- ;; first item in the list. Without an argument, repair the
- ;; list.
- (let* ((begin (org-element-property :contents-begin context))
- (beginm (move-marker (make-marker) begin))
- (struct (org-element-property :structure context))
- (old-struct (copy-tree struct))
- (first-box (save-excursion
- (goto-char begin)
- (looking-at org-list-full-item-re)
- (match-string-no-properties 3)))
- (new-box (cond ((equal arg '(16)) "[-]")
- ((equal arg '(4)) (unless first-box "[ ]"))
- ((equal first-box "[X]") "[ ]")
- (t "[X]"))))
- (cond
- (arg
- (mapc (lambda (pos) (org-list-set-checkbox pos struct new-box))
- (org-list-get-all-items
- begin struct (org-list-prevs-alist struct))))
- ((and first-box (eq (point) begin))
- ;; For convenience, when point is at bol on the first
- ;; item of the list and no argument is provided, simply
- ;; toggle checkbox of that item, if any.
- (org-list-set-checkbox begin struct new-box)))
- (org-list-write-struct
- struct (org-list-parents-alist struct) old-struct)
- (org-update-checkbox-count-maybe)
- (save-excursion (goto-char beginm) (org-list-send-list 'maybe))))
- ((property-drawer node-property)
- (call-interactively 'org-property-action))
- ((radio-target target)
- (call-interactively 'org-update-radio-target-regexp))
- (statistics-cookie
- (call-interactively 'org-update-statistics-cookies))
- ((table table-cell table-row)
- ;; At a table, recalculate every field and align it. Also
- ;; send the table if necessary. If the table has
- ;; a `table.el' type, just give up. At a table row or
- ;; cell, maybe recalculate line but always align table.
- (if (eq (org-element-property :type context) 'table.el)
- (message "%s" "Use C-c ' to edit table.el tables")
- (let ((org-enable-table-editor t))
- (if (or (eq type 'table)
- ;; Check if point is at a TBLFM line.
- (and (eq type 'table-row)
- (= (point) (org-element-property :end context))))
- (save-excursion
- (if (org-at-TBLFM-p)
- (progn (require 'org-table)
- (org-table-calc-current-TBLFM))
- (goto-char (org-element-property :contents-begin context))
- (org-call-with-arg 'org-table-recalculate (or arg t))
- (orgtbl-send-table 'maybe)))
- (org-table-maybe-eval-formula)
- (cond (arg (call-interactively 'org-table-recalculate))
- ((org-table-maybe-recalculate-line))
- (t (org-table-align)))))))
- (timestamp (org-timestamp-change 0 'day))
- (otherwise
- (or (run-hook-with-args-until-success 'org-ctrl-c-ctrl-c-final-hook)
- (user-error
- "C-c C-c can do nothing useful at this location")))))))))
+ (org-update-dblock)))
+ (`footnote-definition
+ (goto-char (org-element-property :post-affiliated context))
+ (call-interactively 'org-footnote-action))
+ (`footnote-reference (call-interactively #'org-footnote-action))
+ ((or `headline `inlinetask)
+ (save-excursion (goto-char (org-element-property :begin context))
+ (call-interactively #'org-set-tags)))
+ (`item
+ ;; At an item: `C-u C-u' sets checkbox to "[-]"
+ ;; unconditionally, whereas `C-u' will toggle its presence.
+ ;; Without a universal argument, if the item has a checkbox,
+ ;; toggle it. Otherwise repair the list.
+ (let* ((box (org-element-property :checkbox context))
+ (struct (org-element-property :structure context))
+ (old-struct (copy-tree struct))
+ (parents (org-list-parents-alist struct))
+ (prevs (org-list-prevs-alist struct))
+ (orderedp (org-not-nil (org-entry-get nil "ORDERED"))))
+ (org-list-set-checkbox
+ (org-element-property :begin context) struct
+ (cond ((equal arg '(16)) "[-]")
+ ((and (not box) (equal arg '(4))) "[ ]")
+ ((or (not box) (equal arg '(4))) nil)
+ ((eq box 'on) "[ ]")
+ (t "[X]")))
+ ;; Mimic `org-list-write-struct' but with grabbing a return
+ ;; value from `org-list-struct-fix-box'.
+ (org-list-struct-fix-ind struct parents 2)
+ (org-list-struct-fix-item-end struct)
+ (org-list-struct-fix-bul struct prevs)
+ (org-list-struct-fix-ind struct parents)
+ (let ((block-item
+ (org-list-struct-fix-box struct parents prevs orderedp)))
+ (if (and box (equal struct old-struct))
+ (if (equal arg '(16))
+ (message "Checkboxes already reset")
+ (user-error "Cannot toggle this checkbox: %s"
+ (if (eq box 'on)
+ "all subitems checked"
+ "unchecked subitems")))
+ (org-list-struct-apply-struct struct old-struct)
+ (org-update-checkbox-count-maybe))
+ (when block-item
+ (message "Checkboxes were removed due to empty box at line %d"
+ (org-current-line block-item))))))
+ (`keyword
+ (let ((org-inhibit-startup-visibility-stuff t)
+ (org-startup-align-all-tables nil))
+ (when (boundp 'org-table-coordinate-overlays)
+ (mapc #'delete-overlay org-table-coordinate-overlays)
+ (setq org-table-coordinate-overlays nil))
+ (org-save-outline-visibility 'use-markers (org-mode-restart)))
+ (message "Local setup has been refreshed"))
+ (`plain-list
+ ;; At a plain list, with a double C-u argument, set
+ ;; checkboxes of each item to "[-]", whereas a single one
+ ;; will toggle their presence according to the state of the
+ ;; first item in the list. Without an argument, repair the
+ ;; list.
+ (let* ((begin (org-element-property :contents-begin context))
+ (beginm (move-marker (make-marker) begin))
+ (struct (org-element-property :structure context))
+ (old-struct (copy-tree struct))
+ (first-box (save-excursion
+ (goto-char begin)
+ (looking-at org-list-full-item-re)
+ (match-string-no-properties 3)))
+ (new-box (cond ((equal arg '(16)) "[-]")
+ ((equal arg '(4)) (unless first-box "[ ]"))
+ ((equal first-box "[X]") "[ ]")
+ (t "[X]"))))
+ (cond
+ (arg
+ (dolist (pos
+ (org-list-get-all-items
+ begin struct (org-list-prevs-alist struct)))
+ (org-list-set-checkbox pos struct new-box)))
+ ((and first-box (eq (point) begin))
+ ;; For convenience, when point is at bol on the first
+ ;; item of the list and no argument is provided, simply
+ ;; toggle checkbox of that item, if any.
+ (org-list-set-checkbox begin struct new-box)))
+ (org-list-write-struct
+ struct (org-list-parents-alist struct) old-struct)
+ (org-update-checkbox-count-maybe)
+ (save-excursion (goto-char beginm) (org-list-send-list 'maybe))))
+ ((or `property-drawer `node-property)
+ (call-interactively #'org-property-action))
+ (`radio-target
+ (call-interactively #'org-update-radio-target-regexp))
+ (`statistics-cookie
+ (call-interactively #'org-update-statistics-cookies))
+ ((or `table `table-cell `table-row)
+ ;; At a table, recalculate every field and align it. Also
+ ;; send the table if necessary. If the table has
+ ;; a `table.el' type, just give up. At a table row or cell,
+ ;; maybe recalculate line but always align table.
+ (if (eq (org-element-property :type context) 'table.el)
+ (message "%s" (substitute-command-keys "\\\
+Use `\\[org-edit-special]' to edit table.el tables"))
+ (let ((org-enable-table-editor t))
+ (if (or (eq type 'table)
+ ;; Check if point is at a TBLFM line.
+ (and (eq type 'table-row)
+ (= (point) (org-element-property :end context))))
+ (save-excursion
+ (if (org-at-TBLFM-p)
+ (progn (require 'org-table)
+ (org-table-calc-current-TBLFM))
+ (goto-char (org-element-property :contents-begin context))
+ (org-call-with-arg 'org-table-recalculate (or arg t))
+ (orgtbl-send-table 'maybe)))
+ (org-table-maybe-eval-formula)
+ (cond (arg (call-interactively #'org-table-recalculate))
+ ((org-table-maybe-recalculate-line))
+ (t (org-table-align)))))))
+ (`timestamp (org-timestamp-change 0 'day))
+ ((and `nil (guard (org-at-heading-p)))
+ ;; When point is on an unsupported object type, we can miss
+ ;; the fact that it also is at a heading. Handle it here.
+ (call-interactively #'org-set-tags))
+ ((guard
+ (run-hook-with-args-until-success 'org-ctrl-c-ctrl-c-final-hook)))
+ (_
+ (user-error
+ (substitute-command-keys
+ "`\\[org-ctrl-c-ctrl-c]' can do nothing useful here"))))))))
(defun org-mode-restart ()
(interactive)
- (let ((indent-status (org-bound-and-true-p org-indent-mode)))
+ (let ((indent-status (bound-and-true-p org-indent-mode)))
(funcall major-mode)
(hack-local-variables)
- (when (and indent-status (not (org-bound-and-true-p org-indent-mode)))
+ (when (and indent-status (not (bound-and-true-p org-indent-mode)))
(org-indent-mode -1)))
(message "%s restarted" major-mode))
(defun org-kill-note-or-show-branches ()
- "If this is a Note buffer, abort storing the note. Else call `show-branches'."
+ "Abort storing current note, or call `outline-show-branches'."
(interactive)
(if (not org-finish-function)
(progn
- (hide-subtree)
- (call-interactively 'show-branches))
+ (outline-hide-subtree)
+ (call-interactively 'outline-show-branches))
(let ((org-note-abort t))
(funcall org-finish-function))))
+(defun org-delete-indentation (&optional arg)
+ "Join current line to previous and fix whitespace at join.
+
+If previous line is a headline add to headline title. Otherwise
+the function calls `delete-indentation'.
+
+With a non-nil optional argument, join it to the following one."
+ (interactive "*P")
+ (if (save-excursion
+ (beginning-of-line (if arg 1 0))
+ (let ((case-fold-search nil))
+ (looking-at org-complex-heading-regexp)))
+ ;; At headline.
+ (let ((tags-column (when (match-beginning 5)
+ (save-excursion (goto-char (match-beginning 5))
+ (current-column))))
+ (string (concat " " (progn (when arg (forward-line 1))
+ (org-trim (delete-and-extract-region
+ (line-beginning-position)
+ (line-end-position)))))))
+ (unless (bobp) (delete-region (point) (1- (point))))
+ (goto-char (or (match-end 4)
+ (match-beginning 5)
+ (match-end 0)))
+ (skip-chars-backward " \t")
+ (save-excursion (insert string))
+ ;; Adjust alignment of tags.
+ (cond
+ ((not tags-column)) ;no tags
+ (org-auto-align-tags (org-set-tags nil t))
+ (t (org--align-tags-here tags-column)))) ;preserve tags column
+ (delete-indentation arg)))
+
(defun org-open-line (n)
"Insert a new row in tables, call `open-line' elsewhere.
-If `org-special-ctrl-o' is nil, just call `open-line' everywhere."
+If `org-special-ctrl-o' is nil, just call `open-line' everywhere.
+As a special case, when a document starts with a table, allow to
+call `open-line' on the very first character."
(interactive "*p")
- (cond
- ((not org-special-ctrl-o)
- (open-line n))
- ((org-at-table-p)
- (org-table-insert-row))
- (t
- (open-line n))))
+ (if (and org-special-ctrl-o (/= (point) 1) (org-at-table-p))
+ (org-table-insert-row)
+ (open-line n)))
(defun org-return (&optional indent)
"Goto next table row or insert a newline.
+
Calls `org-table-next-row' or `newline', depending on context.
-See the individual commands for more information."
+
+When optional INDENT argument is non-nil, call
+`newline-and-indent' instead of `newline'.
+
+When `org-return-follows-link' is non-nil and point is on
+a timestamp or a link, call `org-open-at-point'. However, it
+will not happen if point is in a table or on a \"dead\"
+object (e.g., within a comment). In these case, you need to use
+`org-open-at-point' directly."
(interactive)
- (let (org-ts-what)
+ (let ((context (if org-return-follows-link (org-element-context)
+ (org-element-at-point))))
(cond
- ((or (bobp) (org-in-src-block-p))
- (if indent (newline-and-indent) (newline)))
- ((org-at-table-p)
+ ;; In a table, call `org-table-next-row'.
+ ((or (and (eq (org-element-type context) 'table)
+ (>= (point) (org-element-property :contents-begin context))
+ (< (point) (org-element-property :contents-end context)))
+ (org-element-lineage context '(table-row table-cell) t))
(org-table-justify-field-maybe)
- (call-interactively 'org-table-next-row))
- ;; when `newline-and-indent' is called within a list, make sure
- ;; text moved stays inside the item.
- ((and (org-in-item-p) indent)
- (if (and (org-at-item-p) (>= (point) (match-end 0)))
- (progn
- (save-match-data (newline))
- (org-indent-line-to (length (match-string 0))))
- (let ((ind (org-get-indentation)))
- (newline)
- (if (org-looking-back org-list-end-re)
- (org-indent-line)
- (org-indent-line-to ind)))))
- ((and org-return-follows-link
- (org-at-timestamp-p t)
- (not (eq org-ts-what 'after)))
- (org-follow-timestamp-link))
+ (call-interactively #'org-table-next-row))
+ ;; On a link or a timestamp, call `org-open-at-point' if
+ ;; `org-return-follows-link' allows it. Tolerate fuzzy
+ ;; locations, e.g., in a comment, as `org-open-at-point'.
((and org-return-follows-link
- (let ((tprop (get-text-property (point) 'face)))
- (or (eq tprop 'org-link)
- (and (listp tprop) (memq 'org-link tprop)))))
- (call-interactively 'org-open-at-point))
- ((and (org-at-heading-p)
- (looking-at
- (org-re "\\([ \t]+\\(:[[:alnum:]_@#%:]+:\\)\\)[ \t]*$")))
- (org-show-entry)
- (end-of-line 1)
- (newline))
+ (or (org-in-regexp org-ts-regexp-both nil t)
+ (org-in-regexp org-tsr-regexp-both nil t)
+ (org-in-regexp org-any-link-re nil t)))
+ (call-interactively #'org-open-at-point))
+ ;; Insert newline in heading, but preserve tags.
+ ((and (not (bolp))
+ (save-excursion (beginning-of-line)
+ (let ((case-fold-search nil))
+ (looking-at org-complex-heading-regexp))))
+ ;; At headline. Split line. However, if point is on keyword,
+ ;; priority cookie or tags, do not break any of them: add
+ ;; a newline after the headline instead.
+ (let ((tags-column (and (match-beginning 5)
+ (save-excursion (goto-char (match-beginning 5))
+ (current-column))))
+ (string
+ (when (and (match-end 4) (org-point-in-group (point) 4))
+ (delete-and-extract-region (point) (match-end 4)))))
+ ;; Adjust tag alignment.
+ (cond
+ ((not (and tags-column string)))
+ (org-auto-align-tags (org-set-tags nil t))
+ (t (org--align-tags-here tags-column))) ;preserve tags column
+ (end-of-line)
+ (org-show-entry)
+ (if indent (newline-and-indent) (newline))
+ (when string (save-excursion (insert (org-trim string))))))
+ ;; In a list, make sure indenting keeps trailing text within.
+ ((and indent
+ (not (eolp))
+ (org-element-lineage context '(item)))
+ (let ((trailing-data
+ (delete-and-extract-region (point) (line-end-position))))
+ (newline-and-indent)
+ (save-excursion (insert trailing-data))))
(t (if indent (newline-and-indent) (newline))))))
(defun org-return-indent ()
@@ -20571,146 +21397,16 @@ Calls `org-table-insert-hline', `org-toggle-item', or
(call-interactively 'org-table-insert-hline))
((org-region-active-p)
(call-interactively 'org-toggle-item))
- ((org-in-item-p)
- (call-interactively 'org-cycle-list-bullet))
- (t
- (call-interactively 'org-toggle-item))))
-
-(defun org-toggle-item (arg)
- "Convert headings or normal lines to items, items to normal lines.
-If there is no active region, only the current line is considered.
-
-If the first non blank line in the region is a headline, convert
-all headlines to items, shifting text accordingly.
-
-If it is an item, convert all items to normal lines.
-
-If it is normal text, change region into a list of items.
-With a prefix argument ARG, change the region in a single item."
- (interactive "P")
- (let ((shift-text
- (function
- ;; Shift text in current section to IND, from point to END.
- ;; The function leaves point to END line.
- (lambda (ind end)
- (let ((min-i 1000) (end (copy-marker end)))
- ;; First determine the minimum indentation (MIN-I) of
- ;; the text.
- (save-excursion
- (catch 'exit
- (while (< (point) end)
- (let ((i (org-get-indentation)))
- (cond
- ;; Skip blank lines and inline tasks.
- ((looking-at "^[ \t]*$"))
- ((looking-at org-outline-regexp-bol))
- ;; We can't find less than 0 indentation.
- ((zerop i) (throw 'exit (setq min-i 0)))
- ((< i min-i) (setq min-i i))))
- (forward-line))))
- ;; Then indent each line so that a line indented to
- ;; MIN-I becomes indented to IND. Ignore blank lines
- ;; and inline tasks in the process.
- (let ((delta (- ind min-i)))
- (while (< (point) end)
- (unless (or (looking-at "^[ \t]*$")
- (looking-at org-outline-regexp-bol))
- (org-indent-line-to (+ (org-get-indentation) delta)))
- (forward-line)))))))
- (skip-blanks
- (function
- ;; Return beginning of first non-blank line, starting from
- ;; line at POS.
- (lambda (pos)
- (save-excursion
- (goto-char pos)
- (skip-chars-forward " \r\t\n")
- (point-at-bol)))))
- beg end)
- ;; Determine boundaries of changes.
- (if (org-region-active-p)
- (setq beg (funcall skip-blanks (region-beginning))
- end (copy-marker (region-end)))
- (setq beg (funcall skip-blanks (point-at-bol))
- end (copy-marker (point-at-eol))))
- ;; Depending on the starting line, choose an action on the text
- ;; between BEG and END.
- (org-with-limited-levels
- (save-excursion
- (goto-char beg)
- (cond
- ;; Case 1. Start at an item: de-itemize. Note that it only
- ;; happens when a region is active: `org-ctrl-c-minus'
- ;; would call `org-cycle-list-bullet' otherwise.
- ((org-at-item-p)
- (while (< (point) end)
- (when (org-at-item-p)
- (skip-chars-forward " \t")
- (delete-region (point) (match-end 0)))
- (forward-line)))
- ;; Case 2. Start at an heading: convert to items.
- ((org-at-heading-p)
- (let* ((bul (org-list-bullet-string "-"))
- (bul-len (length bul))
- ;; Indentation of the first heading. It should be
- ;; relative to the indentation of its parent, if any.
- (start-ind (save-excursion
- (cond
- ((not org-adapt-indentation) 0)
- ((not (outline-previous-heading)) 0)
- (t (length (match-string 0))))))
- ;; Level of first heading. Further headings will be
- ;; compared to it to determine hierarchy in the list.
- (ref-level (org-reduced-level (org-outline-level))))
- (while (< (point) end)
- (let* ((level (org-reduced-level (org-outline-level)))
- (delta (max 0 (- level ref-level))))
- ;; If current headline is less indented than the first
- ;; one, set it as reference, in order to preserve
- ;; subtrees.
- (when (< level ref-level) (setq ref-level level))
- (replace-match bul t t)
- (org-indent-line-to (+ start-ind (* delta bul-len)))
- ;; Ensure all text down to END (or SECTION-END) belongs
- ;; to the newly created item.
- (let ((section-end (save-excursion
- (or (outline-next-heading) (point)))))
- (forward-line)
- (funcall shift-text
- (+ start-ind (* (1+ delta) bul-len))
- (min end section-end)))))))
- ;; Case 3. Normal line with ARG: make the first line of region
- ;; an item, and shift indentation of others lines to
- ;; set them as item's body.
- (arg (let* ((bul (org-list-bullet-string "-"))
- (bul-len (length bul))
- (ref-ind (org-get-indentation)))
- (skip-chars-forward " \t")
- (insert bul)
- (forward-line)
- (while (< (point) end)
- ;; Ensure that lines less indented than first one
- ;; still get included in item body.
- (funcall shift-text
- (+ ref-ind bul-len)
- (min end (save-excursion (or (outline-next-heading)
- (point)))))
- (forward-line))))
- ;; Case 4. Normal line without ARG: turn each non-item line
- ;; into an item.
- (t
- (while (< (point) end)
- (unless (or (org-at-heading-p) (org-at-item-p))
- (if (looking-at "\\([ \t]*\\)\\(\\S-\\)")
- (replace-match
- (concat "\\1" (org-list-bullet-string "-") "\\2"))))
- (forward-line))))))))
+ ((org-in-item-p)
+ (call-interactively 'org-cycle-list-bullet))
+ (t
+ (call-interactively 'org-toggle-item))))
(defun org-toggle-heading (&optional nstars)
"Convert headings to normal text, or items or text to headings.
If there is no active region, only convert the current line.
-With a \\[universal-argument] prefix, convert the whole list at
+With a `\\[universal-argument]' prefix, convert the whole list at
point into heading.
In a region:
@@ -20746,7 +21442,7 @@ number of stars to add."
;; do not consider the last line to be in the region.
(when (and current-prefix-arg (org-at-item-p))
- (if (listp current-prefix-arg) (setq current-prefix-arg 1))
+ (when (listp current-prefix-arg) (setq current-prefix-arg 1))
(org-mark-element))
(if (org-region-active-p)
@@ -20771,31 +21467,17 @@ number of stars to add."
;; Case 2. Started at an item: change items into headlines.
;; One star will be added by `org-list-to-subtree'.
((org-at-item-p)
- (let* ((stars (make-string
- ;; subtract the star that will be added again by
- ;; `org-list-to-subtree'
- (if (numberp nstars) (1- nstars)
- (or (org-current-level) 0))
- ?*))
- (add-stars
- (cond (nstars "") ; stars from prefix only
- ((equal stars "") "") ; before first heading
- (org-odd-levels-only "*") ; inside heading, odd
- (t "")))) ; inside heading, oddeven
- (while (< (point) end)
- (when (org-at-item-p)
- ;; Pay attention to cases when region ends before list.
- (let* ((struct (org-list-struct))
- (list-end (min (org-list-get-bottom-point struct) (1+ end))))
- (save-restriction
- (narrow-to-region (point) list-end)
- (insert
- (org-list-to-subtree
- (org-list-parse-list t)
- `(:istart (concat ',stars ',add-stars (funcall get-stars depth))
- :icount (concat ',stars ',add-stars (funcall get-stars depth)))))))
- (setq toggled t))
- (forward-line))))
+ (while (< (point) end)
+ (when (org-at-item-p)
+ ;; Pay attention to cases when region ends before list.
+ (let* ((struct (org-list-struct))
+ (list-end
+ (min (org-list-get-bottom-point struct) (1+ end))))
+ (save-restriction
+ (narrow-to-region (point) list-end)
+ (insert (org-list-to-subtree (org-list-to-lisp t)) "\n")))
+ (setq toggled t))
+ (forward-line)))
;; Case 3. Started at normal text: make every line an heading,
;; skipping headlines and items.
(t (let* ((stars
@@ -20807,7 +21489,7 @@ number of stars to add."
(org-odd-levels-only "**") ; inside heading, odd
(t "*"))) ; inside heading, oddeven
(rpl (concat stars add-stars " "))
- (lend (if (listp nstars) (save-excursion (end-of-line) (point)))))
+ (lend (when (listp nstars) (save-excursion (end-of-line) (point)))))
(while (< (point) (if (equal nstars '(4)) lend end))
(when (and (not (or (org-at-heading-p) (org-at-item-p) (org-at-comment-p)))
(looking-at "\\([ \t]*\\)\\(\\S-\\)"))
@@ -20822,17 +21504,8 @@ on context. See the individual commands for more information."
(interactive)
(org-check-before-invisible-edit 'insert)
(or (run-hook-with-args-until-success 'org-metareturn-hook)
- (let* ((element (org-element-at-point))
- (type (org-element-type element)))
- (when (eq type 'table-row)
- (setq element (org-element-property :parent element))
- (setq type 'table))
- (if (and (eq type 'table)
- (eq (org-element-property :type element) 'org)
- (>= (point) (org-element-property :contents-begin element))
- (< (point) (org-element-property :contents-end element)))
- (call-interactively 'org-table-wrap-region)
- (call-interactively 'org-insert-heading)))))
+ (call-interactively (if (org-at-table-p) #'org-table-wrap-region
+ #'org-insert-heading))))
;;; Menu entries
@@ -20841,7 +21514,7 @@ on context. See the individual commands for more information."
(and (not (org-before-first-heading-p))
(not (org-at-table-p))))
-;; Define the Org-mode menus
+;; Define the Org mode menus
(easy-menu-define org-tbl-menu org-mode-map "Tbl menu"
'("Tbl"
["Align" org-ctrl-c-ctrl-c :active (org-at-table-p)]
@@ -20888,11 +21561,11 @@ on context. See the individual commands for more information."
["Which Column?" org-table-current-column (org-at-table-p)])
["Debug Formulas"
org-table-toggle-formula-debugger
- :style toggle :selected (org-bound-and-true-p org-table-formula-debug)]
+ :style toggle :selected (bound-and-true-p org-table-formula-debug)]
["Show Col/Row Numbers"
org-table-toggle-coordinate-overlays
:style toggle
- :selected (org-bound-and-true-p org-table-overlay-coordinates)]
+ :selected (bound-and-true-p org-table-overlay-coordinates)]
"--"
["Create" org-table-create (and (not (org-at-table-p))
org-enable-table-editor)]
@@ -20900,7 +21573,11 @@ on context. See the individual commands for more information."
["Import from File" org-table-import (not (org-at-table-p))]
["Export to File" org-table-export (org-at-table-p)]
"--"
- ["Create/Convert from/to table.el" org-table-create-with-table.el t]))
+ ["Create/Convert from/to table.el" org-table-create-with-table.el t]
+ "--"
+ ("Plot"
+ ["Ascii plot" orgtbl-ascii-plot :active (org-at-table-p) :keys "C-c \" a"]
+ ["Gnuplot" org-plot/gnuplot :active (org-at-table-p) :keys "C-c \" g"])))
(easy-menu-define org-org-menu org-mode-map "Org menu"
'("Org"
@@ -20909,7 +21586,7 @@ on context. See the individual commands for more information."
["Cycle Global Visibility" org-shifttab :active (not (org-at-table-p))]
["Sparse Tree..." org-sparse-tree t]
["Reveal Context" org-reveal t]
- ["Show All" show-all t]
+ ["Show All" outline-show-all t]
"--"
["Subtree to indirect buffer" org-tree-to-indirect-buffer t])
"--"
@@ -20925,8 +21602,8 @@ on context. See the individual commands for more information."
("Edit Structure"
["Refile Subtree" org-refile (org-in-subtree-not-table-p)]
"--"
- ["Move Subtree Up" org-shiftmetaup (org-in-subtree-not-table-p)]
- ["Move Subtree Down" org-shiftmetadown (org-in-subtree-not-table-p)]
+ ["Move Subtree Up" org-metaup (org-at-heading-p)]
+ ["Move Subtree Down" org-metadown (org-at-heading-p)]
"--"
["Copy Subtree" org-copy-special (org-in-subtree-not-table-p)]
["Cut Subtree" org-cut-special (org-in-subtree-not-table-p)]
@@ -21012,7 +21689,7 @@ on context. See the individual commands for more information."
"--"
["Set property" org-set-property (not (org-before-first-heading-p))]
["Column view of properties" org-columns t]
- ["Insert Column View DBlock" org-insert-columns-dblock t])
+ ["Insert Column View DBlock" org-columns-insert-dblock t])
("Dates and Scheduling"
["Timestamp" org-time-stamp (not (org-before-first-heading-p))]
["Timestamp (inactive)" org-time-stamp-inactive (not (org-before-first-heading-p))]
@@ -21073,9 +21750,7 @@ on context. See the individual commands for more information."
["Insert math symbol" cdlatex-math-symbol (fboundp 'cdlatex-math-symbol)]
["Modify math symbol" org-cdlatex-math-modify
(org-inside-LaTeX-fragment-p)]
- ["Insert citation" org-reftex-citation t]
- "--"
- ["Template for BEAMER" (org-beamer-insert-options-template) t])
+ ["Insert citation" org-reftex-citation t])
"--"
("MobileOrg"
["Push Files and Views" org-mobile-push t]
@@ -21101,20 +21776,20 @@ on context. See the individual commands for more information."
))
(defun org-info (&optional node)
- "Read documentation for Org-mode in the info system.
+ "Read documentation for Org in the info system.
With optional NODE, go directly to that node."
(interactive)
(info (format "(org)%s" (or node ""))))
;;;###autoload
(defun org-submit-bug-report ()
- "Submit a bug report on Org-mode via mail.
+ "Submit a bug report on Org via mail.
Don't hesitate to report any problems or inaccurate documentation.
If you don't have setup sending mail from (X)Emacs, please copy the
output buffer into your mail program, as it gives us important
-information about your Org-mode version and configuration."
+information about your Org version and configuration."
(interactive)
(require 'reporter)
(defvar reporter-prompt-for-summary-p)
@@ -21126,12 +21801,12 @@ information about your Org-mode version and configuration."
(org-version nil 'full)
(let (list)
(save-window-excursion
- (org-pop-to-buffer-same-window (get-buffer-create "*Warn about privacy*"))
+ (pop-to-buffer-same-window (get-buffer-create "*Warn about privacy*"))
(delete-other-windows)
(erase-buffer)
- (insert "You are about to submit a bug report to the Org-mode mailing list.
+ (insert "You are about to submit a bug report to the Org mailing list.
-We would like to add your full Org-mode and Outline configuration to the
+We would like to add your full Org and Outline configuration to the
bug report. This greatly simplifies the work of the maintainer and
other experts on the mailing list.
@@ -21141,7 +21816,7 @@ appear in the form of file names, tags, todo states, or search strings.
If you answer yes to the prompt, you might want to check and remove
such private information before sending the email.")
(add-text-properties (point-min) (point-max) '(face org-warning))
- (when (yes-or-no-p "Include your Org-mode configuration ")
+ (when (yes-or-no-p "Include your Org configuration ")
(mapatoms
(lambda (v)
(and (boundp v)
@@ -21160,11 +21835,11 @@ what in fact did happen. You don't know how to make a good report? See
http://orgmode.org/manual/Feedback.html#Feedback
-Your bug report will be posted to the Org-mode mailing list.
+Your bug report will be posted to the Org mailing list.
------------------------------------------------------------------------")
(save-excursion
- (if (re-search-backward "^\\(Subject: \\)Org-mode version \\(.*?\\);[ \t]*\\(.*\\)" nil t)
- (replace-match "\\1Bug: \\3 [\\2]")))))
+ (when (re-search-backward "^\\(Subject: \\)Org mode version \\(.*?\\);[ \t]*\\(.*\\)" nil t)
+ (replace-match "\\1Bug: \\3 [\\2]")))))
(defun org-install-agenda-files-menu ()
@@ -21172,7 +21847,7 @@ Your bug report will be posted to the Org-mode mailing list.
(save-excursion
(while bl
(set-buffer (pop bl))
- (if (derived-mode-p 'org-mode) (setq bl nil)))
+ (when (derived-mode-p 'org-mode) (setq bl nil)))
(when (derived-mode-p 'org-mode)
(easy-menu-change
'("Org") "File List for Agenda"
@@ -21190,7 +21865,7 @@ Your bug report will be posted to the Org-mode mailing list.
(defun org-require-autoloaded-modules ()
(interactive)
- (mapc 'require
+ (mapc #'require
'(org-agenda org-archive org-attach org-clock org-colview org-id
org-table org-timer)))
@@ -21203,13 +21878,8 @@ With prefix arg UNCOMPILED, load the uncompiled versions."
(let* ((org-dir (org-find-library-dir "org"))
(contrib-dir (or (org-find-library-dir "org-contribdir") org-dir))
(feature-re "^\\(org\\|ob\\|ox\\)\\(-.*\\)?")
- (remove-re (mapconcat 'identity
- (mapcar (lambda (f) (concat "^" f "$"))
- (list (if (featurep 'xemacs)
- "org-colview"
- "org-colview-xemacs")
- "org" "org-loaddefs" "org-version"))
- "\\|"))
+ (remove-re (format "\\`%s\\'"
+ (regexp-opt '("org" "org-loaddefs" "org-version"))))
(feats (delete-dups
(mapcar 'file-name-sans-extension
(mapcar 'file-name-nondirectory
@@ -21241,9 +21911,9 @@ With prefix arg UNCOMPILED, load the uncompiled versions."
't)
f))
lfeat)))
- (if load-uncore
- (message "The following feature%s found in load-path, please check if that's correct:\n%s"
- (if (> (length load-uncore) 1) "s were" " was") load-uncore))
+ (when load-uncore
+ (message "The following feature%s found in load-path, please check if that's correct:\n%s"
+ (if (> (length load-uncore) 1) "s were" " was") load-uncore))
(if load-misses
(message "Some error occurred while reloading Org feature%s\n%s\nPlease check *Messages*!\n%s"
(if (> (length load-misses) 1) "s" "") load-misses (org-version nil 'full))
@@ -21258,7 +21928,7 @@ With prefix arg UNCOMPILED, load the uncompiled versions."
(customize-browse 'org))
(defun org-create-customize-menu ()
- "Create a full customization menu for Org-mode, insert it into the menu."
+ "Create a full customization menu for Org mode, insert it into the menu."
(interactive)
(org-load-modules-maybe)
(org-require-autoloaded-modules)
@@ -21281,9 +21951,9 @@ With prefix arg UNCOMPILED, load the uncompiled versions."
;;; Generally useful functions
-(defun org-get-at-bol (property)
- "Get text property PROPERTY at beginning of line."
- (get-text-property (point-at-bol) property))
+(defun org-get-at-eol (property n)
+ "Get text property PROPERTY at the end of line less N characters."
+ (get-text-property (- (point-at-eol) n) property))
(defun org-find-text-property-in-string (prop s)
"Return the first non-nil value of property PROP in string S."
@@ -21291,19 +21961,9 @@ With prefix arg UNCOMPILED, load the uncompiled versions."
(get-text-property (or (next-single-property-change 0 prop s) 0)
prop s)))
-(defun org-display-warning (message) ;; Copied from Emacs-Muse
+(defun org-display-warning (message)
"Display the given MESSAGE as a warning."
- (if (fboundp 'display-warning)
- (display-warning 'org message
- (if (featurep 'xemacs) 'warning :warning))
- (let ((buf (get-buffer-create "*Org warnings*")))
- (with-current-buffer buf
- (goto-char (point-max))
- (insert "Warning (Org): " message)
- (unless (bolp)
- (newline)))
- (display-buffer buf)
- (sit-for 0))))
+ (display-warning 'org message :warning))
(defun org-eval (form)
"Eval FORM and return result."
@@ -21322,17 +21982,6 @@ With prefix arg UNCOMPILED, load the uncompiled versions."
(>= (match-end 0) pos)
start))))
-(defun org-in-commented-line ()
- "Is point in a line starting with `#'?"
- (equal (char-after (point-at-bol)) ?#))
-
-(defun org-in-indented-comment-line ()
- "Is point in a line starting with `#' after some white space?"
- (save-excursion
- (save-match-data
- (goto-char (point-at-bol))
- (looking-at "[ \t]*#"))))
-
(defun org-in-verbatim-emphasis ()
(save-match-data
(and (org-in-regexp org-emph-re 2)
@@ -21340,14 +21989,35 @@ With prefix arg UNCOMPILED, load the uncompiled versions."
(<= (point) (match-end 4))
(member (match-string 3) '("=" "~")))))
+(defun org-overlay-display (ovl text &optional face evap)
+ "Make overlay OVL display TEXT with face FACE."
+ (overlay-put ovl 'display text)
+ (if face (overlay-put ovl 'face face))
+ (if evap (overlay-put ovl 'evaporate t)))
+
+(defun org-overlay-before-string (ovl text &optional face evap)
+ "Make overlay OVL display TEXT with face FACE."
+ (if face (org-add-props text nil 'face face))
+ (overlay-put ovl 'before-string text)
+ (if evap (overlay-put ovl 'evaporate t)))
+
+(defun org-find-overlays (prop &optional pos delete)
+ "Find all overlays specifying PROP at POS or point.
+If DELETE is non-nil, delete all those overlays."
+ (let (found)
+ (dolist (ov (overlays-at (or pos (point))) found)
+ (cond ((not (overlay-get ov prop)))
+ (delete (delete-overlay ov))
+ (t (push ov found))))))
+
(defun org-goto-marker-or-bmk (marker &optional bookmark)
"Go to MARKER, widen if necessary. When marker is not live, try BOOKMARK."
(if (and marker (marker-buffer marker)
(buffer-live-p (marker-buffer marker)))
(progn
- (org-pop-to-buffer-same-window (marker-buffer marker))
- (if (or (> marker (point-max)) (< marker (point-min)))
- (widen))
+ (pop-to-buffer-same-window (marker-buffer marker))
+ (when (or (> marker (point-max)) (< marker (point-min)))
+ (widen))
(goto-char marker)
(org-show-context 'org-goto))
(if bookmark
@@ -21390,7 +22060,7 @@ upon the next fontification round."
l))
(defun org-shorten-string (s maxlength)
- "Shorten string S so tht it is no longer than MAXLENGTH characters.
+ "Shorten string S so that it is no longer than MAXLENGTH characters.
If the string is shorter or has length MAXLENGTH, just return the
original string. If it is longer, the functions finds a space in the
string, breaks this string off at that locations and adds three dots
@@ -21410,8 +22080,8 @@ if necessary."
"Get the indentation of the current line, interpreting tabs.
When LINE is given, assume it represents a line and compute its indentation."
(if line
- (if (string-match "^ *" (org-remove-tabs line))
- (match-end 0))
+ (when (string-match "^ *" (org-remove-tabs line))
+ (match-end 0))
(save-excursion
(beginning-of-line 1)
(skip-chars-forward " \t")
@@ -21448,35 +22118,45 @@ leave it alone. If it is larger than ind, set it to the target."
(let* ((l (org-remove-tabs line))
(i (org-get-indentation l))
(i1 (car ind)) (i2 (cdr ind)))
- (if (>= i i2) (setq l (substring line i2)))
+ (when (>= i i2) (setq l (substring line i2)))
(if (> i1 0)
(concat (make-string i1 ?\ ) l)
l)))
(defun org-remove-indentation (code &optional n)
- "Remove the maximum common indentation from the lines in CODE.
-N may optionally be the number of spaces to remove."
+ "Remove maximum common indentation in string CODE and return it.
+N may optionally be the number of columns to remove. Return CODE
+as-is if removal failed."
(with-temp-buffer
(insert code)
- (org-do-remove-indentation n)
- (buffer-string)))
+ (if (org-do-remove-indentation n) (buffer-string) code)))
(defun org-do-remove-indentation (&optional n)
- "Remove the maximum common indentation from the buffer."
- (untabify (point-min) (point-max))
- (let ((min 10000) re)
- (if n
- (setq min n)
- (goto-char (point-min))
- (while (re-search-forward "^ *[^ \n]" nil t)
- (setq min (min min (1- (- (match-end 0) (match-beginning 0)))))))
- (unless (or (= min 0) (= min 10000))
- (setq re (format "^ \\{%d\\}" min))
- (goto-char (point-min))
- (while (re-search-forward re nil t)
- (replace-match "")
- (end-of-line 1))
- min)))
+ "Remove the maximum common indentation from the buffer.
+When optional argument N is a positive integer, remove exactly
+that much characters from indentation, if possible. Return nil
+if it fails."
+ (catch :exit
+ (goto-char (point-min))
+ ;; Find maximum common indentation, if not specified.
+ (let ((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 nil)
+ (setq min-ind (min min-ind ind))))))
+ min-ind))))
+ (if (zerop n) (throw :exit nil)
+ ;; 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 nil))
+ (t (indent-line-to (- ind n))))
+ (forward-line)))
+ ;; Signal success.
+ t))))
(defun org-fill-template (template alist)
"Find each %key of ALIST in TEMPLATE and replace it."
@@ -21496,12 +22176,6 @@ N may optionally be the number of spaces to remove."
(or (buffer-base-buffer buffer)
buffer)))
-(defun org-trim (s)
- "Remove whitespace at beginning and end of string."
- (if (string-match "\\`[ \t\n\r]+" s) (setq s (replace-match "" t t s)))
- (if (string-match "[ \t\n\r]+\\'" s) (setq s (replace-match "" t t s)))
- s)
-
(defun org-wrap (string &optional width lines)
"Wrap string to either a number of lines, or a width in characters.
If WIDTH is non-nil, the string is wrapped to that width, however many lines
@@ -21539,13 +22213,12 @@ The return value is a list of lines, without newlines at the end."
(defun org-split-string (string &optional separators)
"Splits STRING into substrings at SEPARATORS.
+SEPARATORS is a regular expression.
No empty strings are returned if there are matches at the beginning
and end of string."
- (let ((rexp (or separators "[ \f\t\n\r\v]+"))
- (start 0)
- notfirst
- (list nil))
- (while (and (string-match rexp string
+ ;; FIXME: why not use (split-string STRING SEPARATORS t)?
+ (let ((start 0) notfirst list)
+ (while (and (string-match (or separators "[ \f\t\n\r\v]+") string
(if (and notfirst
(= start (match-beginning 0))
(< start (length string)))
@@ -21555,14 +22228,10 @@ and end of string."
(or (eq (match-beginning 0) 0)
(and (eq (match-beginning 0) (match-end 0))
(eq (match-beginning 0) start))
- (setq list
- (cons (substring string start (match-beginning 0))
- list)))
+ (push (substring string start (match-beginning 0)) list))
(setq start (match-end 0)))
(or (eq start (length string))
- (setq list
- (cons (substring string start)
- list)))
+ (push (substring string start) list))
(nreverse list)))
(defun org-quote-vert (s)
@@ -21579,10 +22248,8 @@ and end of string."
"Whether point is in a code source block.
When INSIDE is non-nil, don't consider we are within a src block
when point is at #+BEGIN_SRC or #+END_SRC."
- (let ((case-fold-search t) ov)
- (or (and (setq ov (overlays-at (point)))
- (memq 'org-block-background
- (overlay-properties (car ov))))
+ (let ((case-fold-search t))
+ (or (and (eq (get-char-property (point) 'src-block) t))
(and (not inside)
(save-match-data
(save-excursion
@@ -21604,13 +22271,13 @@ contexts are:
:item on the first line of a plain list item
:item-bullet on the bullet/number of a plain list item
:checkbox on the checkbox in a plain list item
-:table in an org-mode table
+:table in an Org table
:table-special on a special filed in a table
:table-table in a table.el table
:clocktable in a clocktable
:src-block in a source block
:link on a hyperlink
-:keyword on a keyword: SCHEDULED, DEADLINE, CLOSE, COMMENT, QUOTE.
+:keyword on a keyword: SCHEDULED, DEADLINE, CLOSE, COMMENT.
:target on a <>
:radio-target on a <<>>
:latex-fragment on a LaTeX fragment
@@ -21635,8 +22302,8 @@ and :keyword."
(push (org-point-in-group p 4 :tags) clist))
(goto-char p)
(skip-chars-backward "^[\n\r \t") (or (bobp) (backward-char 1))
- (if (looking-at "\\[#[A-Z0-9]\\]")
- (push (org-point-in-group p 0 :priority) clist)))
+ (when (looking-at "\\[#[A-Z0-9]\\]")
+ (push (org-point-in-group p 0 :priority) clist)))
((org-at-item-p)
(push (org-point-in-group p 2 :item-bullet) clist)
@@ -21648,10 +22315,10 @@ and :keyword."
((org-at-table-p)
(push (list :table (org-table-begin) (org-table-end)) clist)
- (if (memq 'org-formula faces)
- (push (list :table-special
- (previous-single-property-change p 'face)
- (next-single-property-change p 'face)) clist)))
+ (when (memq 'org-formula faces)
+ (push (list :table-special
+ (previous-single-property-change p 'face)
+ (next-single-property-change p 'face)) clist)))
((org-at-table-p 'any)
(push (list :table-table) clist)))
(goto-char p)
@@ -21660,16 +22327,16 @@ and :keyword."
;; New the "medium" contexts: clocktables, source blocks
(cond ((org-in-clocktable-p)
(push (list :clocktable
- (and (or (looking-at "#\\+BEGIN: clocktable")
- (search-backward "#+BEGIN: clocktable" nil t))
- (match-beginning 0))
- (and (re-search-forward "#\\+END:?" nil t)
+ (and (or (looking-at "[ \t]*\\(#\\+BEGIN: clocktable\\)")
+ (re-search-backward "[ \t]*\\(#+BEGIN: clocktable\\)" nil t))
+ (match-beginning 1))
+ (and (re-search-forward "[ \t]*#\\+END:?" nil t)
(match-end 0))) clist))
((org-in-src-block-p)
(push (list :src-block
- (and (or (looking-at "#\\+BEGIN_SRC")
- (search-backward "#+BEGIN_SRC" nil t))
- (match-beginning 0))
+ (and (or (looking-at "[ \t]*\\(#\\+BEGIN_SRC\\)")
+ (re-search-backward "[ \t]*\\(#+BEGIN_SRC\\)" nil t))
+ (match-beginning 1))
(and (search-forward "#+END_SRC" nil t)
(match-beginning 0))) clist))))
(goto-char p)
@@ -21689,14 +22356,14 @@ and :keyword."
((org-at-target-p)
(push (org-point-in-group p 0 :target) clist)
(goto-char (1- (match-beginning 0)))
- (if (looking-at org-radio-target-regexp)
- (push (org-point-in-group p 0 :radio-target) clist))
+ (when (looking-at org-radio-target-regexp)
+ (push (org-point-in-group p 0 :radio-target) clist))
(goto-char p))
- ((setq o (car (delq nil
- (mapcar
- (lambda (x)
- (if (memq x org-latex-fragment-image-overlays) x))
- (overlays-at (point))))))
+ ((setq o (cl-some
+ (lambda (o)
+ (and (eq (overlay-get o 'org-overlay-type) 'org-latex-overlay)
+ o))
+ (overlays-at (point))))
(push (list :latex-fragment
(overlay-start o) (overlay-end o)) clist)
(push (list :latex-preview
@@ -21708,35 +22375,27 @@ and :keyword."
(setq clist (nreverse (delq nil clist)))
clist))
-;; FIXME: Compare with at-regexp-p Do we need both?
-(defun org-in-regexp (re &optional nlines visually)
- "Check if point is inside a match of regexp.
-Normally only the current line is checked, but you can include NLINES extra
-lines both before and after point into the search.
-If VISUALLY is set, require that the cursor is not after the match but
-really on, so that the block visually is on the match."
- (catch 'exit
+(defun org-in-regexp (regexp &optional nlines visually)
+ "Check if point is inside a match of REGEXP.
+
+Normally only the current line is checked, but you can include
+NLINES extra lines around point into the search. If VISUALLY is
+set, require that the cursor is not after the match but really
+on, so that the block visually is on the match.
+
+Return nil or a cons cell (BEG . END) where BEG and END are,
+respectively, the positions at the beginning and the end of the
+match."
+ (catch :exit
(let ((pos (point))
- (eol (point-at-eol (+ 1 (or nlines 0))))
- (inc (if visually 1 0)))
+ (eol (line-end-position (if nlines (1+ nlines) 1))))
(save-excursion
(beginning-of-line (- 1 (or nlines 0)))
- (while (re-search-forward re eol t)
- (if (and (<= (match-beginning 0) pos)
- (>= (+ inc (match-end 0)) pos))
- (throw 'exit (cons (match-beginning 0) (match-end 0)))))))))
-
-(defun org-at-regexp-p (regexp)
- "Is point inside a match of REGEXP in the current line?"
- (catch 'exit
- (save-excursion
- (let ((pos (point)) (end (point-at-eol)))
- (beginning-of-line 1)
- (while (re-search-forward regexp end t)
- (if (and (<= (match-beginning 0) pos)
- (>= (match-end 0) pos))
- (throw 'exit t)))
- nil))))
+ (while (and (re-search-forward regexp eol t)
+ (<= (match-beginning 0) pos))
+ (let ((end (match-end 0)))
+ (when (or (> end pos) (and (= end pos) (not visually)))
+ (throw :exit (cons (match-beginning 0) (match-end 0))))))))))
(defun org-between-regexps-p (start-re end-re &optional lim-up lim-down)
"Non-nil when point is between matches of START-RE and END-RE.
@@ -21757,7 +22416,7 @@ position before START-RE (resp. after END-RE)."
(save-excursion
;; Point is on a block when on START-RE or if START-RE can be
;; found before it...
- (and (or (org-at-regexp-p start-re)
+ (and (or (org-in-regexp start-re)
(re-search-backward start-re limit-up t))
(setq beg (match-beginning 0))
;; ... and END-RE after it...
@@ -21783,27 +22442,15 @@ block from point."
(let ((case-fold-search t)
(lim-up (save-excursion (outline-previous-heading)))
(lim-down (save-excursion (outline-next-heading))))
- (mapc (lambda (name)
- (let ((n (regexp-quote name)))
- (when (org-between-regexps-p
- (concat "^[ \t]*#\\+begin_" n)
- (concat "^[ \t]*#\\+end_" n)
- lim-up lim-down)
- (throw 'exit n))))
- names))
+ (dolist (name names)
+ (let ((n (regexp-quote name)))
+ (when (org-between-regexps-p
+ (concat "^[ \t]*#\\+begin_" n)
+ (concat "^[ \t]*#\\+end_" n)
+ lim-up lim-down)
+ (throw 'exit n)))))
nil)))
-(defun org-in-drawer-p ()
- "Is point within a drawer?"
- (save-match-data
- (let ((case-fold-search t)
- (lim-up (save-excursion (outline-previous-heading)))
- (lim-down (save-excursion (outline-next-heading))))
- (org-between-regexps-p
- (concat "^[ \t]*:" (regexp-opt org-drawers) ":")
- "^[ \t]*:end:.*$"
- lim-up lim-down))))
-
(defun org-occur-in-agenda-files (regexp &optional _nlines)
"Call `multi-occur' with buffers for all agenda files."
(interactive "sOrg-files matching: ")
@@ -21815,40 +22462,21 @@ block from point."
(setq files (org-add-archive-files files)))
(dolist (f extra)
(unless (member (file-truename f) tnames)
- (unless (member f files) (setq files (append files (list f))))
- (setq tnames (append tnames (list (file-truename f))))))
+ (unless (member f files) (setq files (append files (list f))))
+ (setq tnames (append tnames (list (file-truename f))))))
(multi-occur
(mapcar (lambda (x)
(with-current-buffer
- ;; FIXME: Why not just (find-file-noselect x)?
- ;; Is it to avoid the "revert buffer" prompt?
+ ;; FIXME: Why not just (find-file-noselect x)?
+ ;; Is it to avoid the "revert buffer" prompt?
(or (get-file-buffer x) (find-file-noselect x))
(widen)
(current-buffer)))
files)
regexp)))
-(if (boundp 'occur-mode-find-occurrence-hook)
- ;; Emacs 23
- (add-hook 'occur-mode-find-occurrence-hook
- (lambda ()
- (when (derived-mode-p 'org-mode)
- (org-reveal))))
- ;; Emacs 22
- (defadvice occur-mode-goto-occurrence
- (after org-occur-reveal activate)
- (and (derived-mode-p 'org-mode) (org-reveal)))
- (defadvice occur-mode-goto-occurrence-other-window
- (after org-occur-reveal activate)
- (and (derived-mode-p 'org-mode) (org-reveal)))
- (defadvice occur-mode-display-occurrence
- (after org-occur-reveal activate)
- (when (derived-mode-p 'org-mode)
- (let ((pos (occur-mode-find-occurrence)))
- (with-current-buffer (marker-buffer pos)
- (save-excursion
- (goto-char pos)
- (org-reveal)))))))
+(add-hook 'occur-mode-find-occurrence-hook
+ (lambda () (when (derived-mode-p 'org-mode) (org-reveal))))
(defun org-occur-link-in-agenda-files ()
"Create a link and search for it in the agendas.
@@ -21878,81 +22506,27 @@ merge (a 1) and (a 3) into (a 1 3).
The function returns the new ALIST."
(let (rtn)
- (mapc
- (lambda (e)
- (let (n)
- (if (not (assoc (car e) rtn))
- (push e rtn)
- (setq n (cons (car e) (append (cdr (assoc (car e) rtn)) (cdr e))))
- (setq rtn (assq-delete-all (car e) rtn))
- (push n rtn))))
- alist)
- rtn))
+ (dolist (e alist rtn)
+ (let (n)
+ (if (not (assoc (car e) rtn))
+ (push e rtn)
+ (setq n (cons (car e) (append (cdr (assoc (car e) rtn)) (cdr e))))
+ (setq rtn (assq-delete-all (car e) rtn))
+ (push n rtn))))))
(defun org-delete-all (elts list)
- "Remove all elements in ELTS from LIST."
+ "Remove all elements in ELTS from LIST.
+Comparison is done with `equal'. It is a destructive operation
+that may remove elements by altering the list structure."
(while elts
(setq list (delete (pop elts) list)))
list)
-(defun org-count (cl-item cl-seq)
- "Count the number of occurrences of ITEM in SEQ.
-Taken from `count' in cl-seq.el with all keyword arguments removed."
- (let ((cl-end (length cl-seq)) (cl-start 0) (cl-count 0) cl-x)
- (when (consp cl-seq) (setq cl-seq (nthcdr cl-start cl-seq)))
- (while (< cl-start cl-end)
- (setq cl-x (if (consp cl-seq) (pop cl-seq) (aref cl-seq cl-start)))
- (if (equal cl-item cl-x) (setq cl-count (1+ cl-count)))
- (setq cl-start (1+ cl-start)))
- cl-count))
-
-(defun org-remove-if (predicate seq)
- "Remove everything from SEQ that fulfills PREDICATE."
- (let (res e)
- (while seq
- (setq e (pop seq))
- (if (not (funcall predicate e)) (push e res)))
- (nreverse res)))
-
-(defun org-remove-if-not (predicate seq)
- "Remove everything from SEQ that does not fulfill PREDICATE."
- (let (res e)
- (while seq
- (setq e (pop seq))
- (if (funcall predicate e) (push e res)))
- (nreverse res)))
-
-(defun org-reduce (cl-func cl-seq &rest cl-keys)
- "Reduce two-argument FUNCTION across SEQ.
-Taken from `reduce' in cl-seq.el with all keyword arguments but
-\":initial-value\" removed."
- (let ((cl-accum (cond ((memq :initial-value cl-keys)
- (cadr (memq :initial-value cl-keys)))
- (cl-seq (pop cl-seq))
- (t (funcall cl-func)))))
- (while cl-seq
- (setq cl-accum (funcall cl-func cl-accum (pop cl-seq))))
- cl-accum))
-
-(defun org-every (pred seq)
- "Return true if PREDICATE is true of every element of SEQ.
-Adapted from `every' in cl.el."
- (catch 'org-every
- (mapc (lambda (e) (unless (funcall pred e) (throw 'org-every nil))) seq)
- t))
-
-(defun org-some (pred seq)
- "Return true if PREDICATE is true of any element of SEQ.
-Adapted from `some' in cl.el."
- (catch 'org-some
- (mapc (lambda (e) (when (funcall pred e) (throw 'org-some t))) seq)
- nil))
-
(defun org-back-over-empty-lines ()
"Move backwards over whitespace, to the beginning of the first empty line.
Returns the number of empty lines passed."
(let ((pos (point)))
- (if (cdr (assoc 'heading org-blank-before-new-entry))
+ (if (cdr (assq 'heading org-blank-before-new-entry))
(skip-chars-backward " \t\n\r")
(unless (eobp)
(forward-line -1)))
@@ -22005,7 +22579,7 @@ so values can contain further %-escapes if they are define later in TABLE."
(let ((tbl (copy-alist table))
(case-fold-search nil)
(pchg 0)
- e re rpl)
+ re rpl)
(dolist (e tbl)
(setq re (concat "%-?[0-9.]*" (substring (car e) 1)))
(when (and (cdr e) (string-match re (cdr e)))
@@ -22023,16 +22597,6 @@ so values can contain further %-escapes if they are define later in TABLE."
(setq string (replace-match sref t t string)))))
string))
-(defun org-sublist (list start end)
- "Return a section of LIST, from START to END.
-Counting starts at 1."
- (let (rtn (c start))
- (setq list (nthcdr (1- start) list))
- (while (and list (<= c end))
- (push (pop list) rtn)
- (setq c (1+ c)))
- (nreverse rtn)))
-
(defun org-find-base-buffer-visiting (file)
"Like `find-buffer-visiting' but always return the base buffer and
not an indirect buffer."
@@ -22042,26 +22606,12 @@ not an indirect buffer."
(or (buffer-base-buffer buf) buf)
nil)))
-(defun org-image-file-name-regexp (&optional extensions)
- "Return regexp matching the file names of images.
-If EXTENSIONS is given, only match these."
- (if (and (not extensions) (fboundp 'image-file-name-regexp))
- (image-file-name-regexp)
- (let ((image-file-name-extensions
- (or extensions
- '("png" "jpeg" "jpg" "gif" "tiff" "tif"
- "xbm" "xpm" "pbm" "pgm" "ppm"))))
- (concat "\\."
- (regexp-opt (nconc (mapcar 'upcase
- image-file-name-extensions)
- image-file-name-extensions)
- t)
- "\\'"))))
-
-(defun org-file-image-p (file &optional extensions)
+;;; TODO: Only called once, from ox-odt which should probably use
+;;; org-export-inline-image-p or something.
+(defun org-file-image-p (file)
"Return non-nil if FILE is an image."
(save-match-data
- (string-match (org-image-file-name-regexp extensions) file)))
+ (string-match (image-file-name-regexp) file)))
(defun org-get-cursor-date (&optional with-time)
"Return the date at cursor in as a time.
@@ -22085,10 +22635,10 @@ the agenda) or the current time of the day."
(nth 1 date) (nth 0 date) (nth 2 date))))
((eq major-mode 'org-agenda-mode)
(setq day (get-text-property (point) 'day))
- (if day
- (setq date (calendar-gregorian-from-absolute day)
- defd (encode-time 0 (or mod 0) (or hod 0)
- (nth 1 date) (nth 0 date) (nth 2 date))))))
+ (when day
+ (setq date (calendar-gregorian-from-absolute day)
+ defd (encode-time 0 (or mod 0) (or hod 0)
+ (nth 1 date) (nth 0 date) (nth 2 date))))))
(or defd (current-time))))
(defun org-mark-subtree (&optional up)
@@ -22101,177 +22651,440 @@ hierarchy of headlines by UP levels before marking the subtree."
(cond ((org-at-heading-p) (beginning-of-line))
((org-before-first-heading-p) (user-error "Not in a subtree"))
(t (outline-previous-visible-heading 1))))
- (when up (while (and (> up 0) (org-up-heading-safe)) (decf up)))
- (if (org-called-interactively-p 'any)
+ (when up (while (and (> up 0) (org-up-heading-safe)) (cl-decf up)))
+ (if (called-interactively-p 'any)
(call-interactively 'org-mark-element)
(org-mark-element)))
+(defun org-file-newer-than-p (file time)
+ "Non-nil if FILE is newer than TIME.
+FILE is a filename, as a string, TIME is a list of integers, as
+returned by, e.g., `current-time'."
+ (and (file-exists-p file)
+ ;; Only compare times up to whole seconds as some file-systems
+ ;; (e.g. HFS+) do not retain any finer granularity. As
+ ;; a consequence, make sure we return non-nil when the two
+ ;; times are equal.
+ (not (time-less-p (cl-subseq (nth 5 (file-attributes file)) 0 2)
+ (cl-subseq time 0 2)))))
+
+(defun org-compile-file (source process ext &optional err-msg log-buf spec)
+ "Compile a SOURCE file using PROCESS.
+
+PROCESS is either a function or a list of shell commands, as
+strings. EXT is a file extension, without the leading dot, as
+a string. It is used to check if the process actually succeeded.
+
+PROCESS must create a file with the same base name and directory
+as SOURCE, but ending with EXT. The function then returns its
+filename. Otherwise, it raises an error. The error message can
+then be refined by providing string ERR-MSG, which is appended to
+the standard message.
+
+If PROCESS is a function, it is called with a single argument:
+the SOURCE file.
+
+If it is a list of commands, each of them is called using
+`shell-command'. By default, in each command, %b, %f, %F, %o and
+%O are replaced with, respectively, SOURCE base name, name, full
+name, directory and absolute output file name. It is possible,
+however, to use more place-holders by specifying them in optional
+argument SPEC, as an alist following the pattern
+
+ (CHARACTER . REPLACEMENT-STRING).
+
+When PROCESS is a list of commands, optional argument LOG-BUF can
+be set to a buffer or a buffer name. `shell-command' then uses
+it for output."
+ (let* ((base-name (file-name-base source))
+ (full-name (file-truename source))
+ (out-dir (or (file-name-directory source) "./"))
+ (output (expand-file-name (concat base-name "." ext) out-dir))
+ (time (current-time))
+ (err-msg (if (stringp err-msg) (concat ". " err-msg) "")))
+ (save-window-excursion
+ (pcase process
+ ((pred functionp) (funcall process (shell-quote-argument source)))
+ ((pred consp)
+ (let ((log-buf (and log-buf (get-buffer-create log-buf)))
+ (spec (append spec
+ `((?b . ,(shell-quote-argument base-name))
+ (?f . ,(shell-quote-argument source))
+ (?F . ,(shell-quote-argument full-name))
+ (?o . ,(shell-quote-argument out-dir))
+ (?O . ,(shell-quote-argument output))))))
+ (dolist (command process)
+ (shell-command (format-spec command spec) log-buf))))
+ (_ (error "No valid command to process %S%s" source err-msg))))
+ ;; Check for process failure. Output file is expected to be
+ ;; located in the same directory as SOURCE.
+ (unless (org-file-newer-than-p output time)
+ (error (format "File %S wasn't produced%s" output err-msg)))
+ output))
;;; Indentation
+(defvar org-element-greater-elements)
+(defun org--get-expected-indentation (element contentsp)
+ "Expected indentation column for current line, according to ELEMENT.
+ELEMENT is an element containing point. CONTENTSP is non-nil
+when indentation is to be computed according to contents of
+ELEMENT."
+ (let ((type (org-element-type element))
+ (start (org-element-property :begin element))
+ (post-affiliated (org-element-property :post-affiliated element)))
+ (org-with-wide-buffer
+ (cond
+ (contentsp
+ (cl-case type
+ ((diary-sexp footnote-definition) 0)
+ ((headline inlinetask nil)
+ (if (not org-adapt-indentation) 0
+ (let ((level (org-current-level)))
+ (if level (1+ level) 0))))
+ ((item plain-list) (org-list-item-body-column post-affiliated))
+ (t
+ (goto-char start)
+ (org-get-indentation))))
+ ((memq type '(headline inlinetask nil))
+ (if (org-match-line "[ \t]*$")
+ (org--get-expected-indentation element t)
+ 0))
+ ((memq type '(diary-sexp footnote-definition)) 0)
+ ;; First paragraph of a footnote definition or an item.
+ ;; Indent like parent.
+ ((< (line-beginning-position) start)
+ (org--get-expected-indentation
+ (org-element-property :parent element) t))
+ ;; At first line: indent according to previous sibling, if any,
+ ;; ignoring footnote definitions and inline tasks, or parent's
+ ;; contents.
+ ((= (line-beginning-position) start)
+ (catch 'exit
+ (while t
+ (if (= (point-min) start) (throw 'exit 0)
+ (goto-char (1- start))
+ (let* ((previous (org-element-at-point))
+ (parent previous))
+ (while (and parent (<= (org-element-property :end parent) start))
+ (setq previous parent
+ parent (org-element-property :parent parent)))
+ (cond
+ ((not previous) (throw 'exit 0))
+ ((> (org-element-property :end previous) start)
+ (throw 'exit (org--get-expected-indentation previous t)))
+ ((memq (org-element-type previous)
+ '(footnote-definition inlinetask))
+ (setq start (org-element-property :begin previous)))
+ (t (goto-char (org-element-property :begin previous))
+ (throw 'exit
+ (if (bolp) (org-get-indentation)
+ ;; At first paragraph in an item or
+ ;; a footnote definition.
+ (org--get-expected-indentation
+ (org-element-property :parent previous) t))))))))))
+ ;; Otherwise, move to the first non-blank line above.
+ (t
+ (beginning-of-line)
+ (let ((pos (point)))
+ (skip-chars-backward " \r\t\n")
+ (cond
+ ;; Two blank lines end a footnote definition or a plain
+ ;; list. When we indent an empty line after them, the
+ ;; containing list or footnote definition is over, so it
+ ;; qualifies as a previous sibling. Therefore, we indent
+ ;; like its first line.
+ ((and (memq type '(footnote-definition plain-list))
+ (> (count-lines (point) pos) 2))
+ (goto-char start)
+ (org-get-indentation))
+ ;; Line above is the first one of a paragraph at the
+ ;; beginning of an item or a footnote definition. Indent
+ ;; like parent.
+ ((< (line-beginning-position) start)
+ (org--get-expected-indentation
+ (org-element-property :parent element) t))
+ ;; Line above is the beginning of an element, i.e., point
+ ;; was originally on the blank lines between element's start
+ ;; and contents.
+ ((= (line-beginning-position) post-affiliated)
+ (org--get-expected-indentation element t))
+ ;; POS is after contents in a greater element. Indent like
+ ;; the beginning of the element.
+ ((and (memq type org-element-greater-elements)
+ (let ((cend (org-element-property :contents-end element)))
+ (and cend (<= cend pos))))
+ ;; As a special case, if point is at the end of a footnote
+ ;; definition or an item, indent like the very last element
+ ;; within. If that last element is an item, indent like
+ ;; its contents.
+ (if (memq type '(footnote-definition item plain-list))
+ (let ((last (org-element-at-point)))
+ (goto-char pos)
+ (org--get-expected-indentation
+ last (eq (org-element-type last) 'item)))
+ (goto-char start)
+ (org-get-indentation)))
+ ;; In any other case, indent like the current line.
+ (t (org-get-indentation)))))))))
+
+(defun org--align-node-property ()
+ "Align node property at point.
+Alignment is done according to `org-property-format', which see."
+ (when (save-excursion
+ (beginning-of-line)
+ (looking-at org-property-re))
+ (replace-match
+ (concat (match-string 4)
+ (org-trim
+ (format org-property-format (match-string 1) (match-string 3))))
+ t t)))
+
(defun org-indent-line ()
- "Indent line depending on context."
+ "Indent line depending on context.
+
+Indentation is done according to the following rules:
+
+ - Footnote definitions, diary sexps, headlines and inline tasks
+ have to start at column 0.
+
+ - On the very first line of an element, consider, in order, the
+ next rules until one matches:
+
+ 1. If there's a sibling element before, ignoring footnote
+ definitions and inline tasks, indent like its first line.
+
+ 2. If element has a parent, indent like its contents. More
+ precisely, if parent is an item, indent after the
+ description part, if any, or the bullet (see
+ `org-list-description-max-indent'). Else, indent like
+ parent's first line.
+
+ 3. Otherwise, indent relatively to current level, if
+ `org-adapt-indentation' is non-nil, or to left margin.
+
+ - On a blank line at the end of an element, indent according to
+ the type of the element. More precisely
+
+ 1. If element is a plain list, an item, or a footnote
+ definition, indent like the very last element within.
+
+ 2. If element is a paragraph, indent like its last non blank
+ line.
+
+ 3. Otherwise, indent like its very first line.
+
+ - In the code part of a source block, use language major mode
+ to indent current line if `org-src-tab-acts-natively' is
+ non-nil. If it is nil, do nothing.
+
+ - Otherwise, indent like the first non-blank line above.
+
+The function doesn't indent an item as it could break the whole
+list structure. Instead, use \\`\\[org-shiftmetaleft]' or \
+`\\[org-shiftmetaright]'.
+
+Also align node properties according to `org-property-format'."
(interactive)
- (let* ((pos (point))
- (itemp (org-at-item-p))
- (case-fold-search t)
- (org-drawer-regexp (or org-drawer-regexp "\000"))
- (inline-task-p (and (featurep 'org-inlinetask)
- (org-inlinetask-in-task-p)))
- (inline-re (and inline-task-p
- (org-inlinetask-outline-regexp)))
- column)
- (if (and orgstruct-is-++ (eq pos (point)))
- (let ((indent-line-function (cadadr (assoc 'indent-line-function org-fb-vars))))
- (indent-according-to-mode))
- (beginning-of-line 1)
- (cond
- ;; Headings
- ((looking-at org-outline-regexp) (setq column 0))
- ;; Footnote definition
- ((looking-at org-footnote-definition-re) (setq column 0))
- ;; Literal examples
- ((looking-at "[ \t]*:\\( \\|$\\)")
- (setq column (org-get-indentation))) ; do nothing
- ;; Lists
- ((ignore-errors (goto-char (org-in-item-p)))
- (setq column (if itemp
- (org-get-indentation)
- (org-list-item-body-column (point))))
- (goto-char pos))
- ;; Drawers
- ((and (looking-at "[ \t]*:END:")
- (save-excursion (re-search-backward org-drawer-regexp nil t)))
- (save-excursion
- (goto-char (1- (match-beginning 1)))
- (setq column (current-column))))
- ;; Special blocks
- ((and (looking-at "[ \t]*#\\+end_\\([a-z]+\\)")
- (save-excursion
- (re-search-backward
- (concat "^[ \t]*#\\+begin_" (downcase (match-string 1))) nil t)))
- (setq column (org-get-indentation (match-string 0))))
- ((and (not (looking-at "[ \t]*#\\+begin_"))
- (org-between-regexps-p "^[ \t]*#\\+begin_" "[ \t]*#\\+end_"))
- (save-excursion
- (re-search-backward "^[ \t]*#\\+begin_\\([a-z]+\\)" nil t))
- (setq column
- (cond ((equal (downcase (match-string 1)) "src")
- ;; src blocks: let `org-edit-src-exit' handle them
- (org-get-indentation))
- ((equal (downcase (match-string 1)) "example")
- (max (org-get-indentation)
- (org-get-indentation (match-string 0))))
- (t
- (org-get-indentation (match-string 0))))))
- ;; This line has nothing special, look at the previous relevant
- ;; line to compute indentation
- (t
- (beginning-of-line 0)
- (while (and (not (bobp))
- (not (looking-at org-table-line-regexp))
- (not (looking-at org-drawer-regexp))
- ;; When point started in an inline task, do not move
- ;; above task starting line.
- (not (and inline-task-p (looking-at inline-re)))
- ;; Skip drawers, blocks, empty lines, verbatim,
- ;; comments, tables, footnotes definitions, lists,
- ;; inline tasks.
- (or (and (looking-at "[ \t]*:END:")
- (re-search-backward org-drawer-regexp nil t))
- (and (looking-at "[ \t]*#\\+end_")
- (re-search-backward "[ \t]*#\\+begin_"nil t))
- (looking-at "[ \t]*[\n:#|]")
- (looking-at org-footnote-definition-re)
- (and (not inline-task-p)
- (featurep 'org-inlinetask)
- (org-inlinetask-in-task-p)
- (or (org-inlinetask-goto-beginning) t))))
- (beginning-of-line 0))
- (cond
- ;; There was a list item above.
- ((ignore-errors (goto-char (org-in-item-p)))
- (goto-char (org-list-get-top-point (org-list-struct)))
- (setq column (org-get-indentation)))
- ;; There was an heading above.
- ((looking-at "\\*+[ \t]+")
- (if (not org-adapt-indentation)
- (setq column 0)
- (goto-char (match-end 0))
- (setq column (current-column))))
- ;; A drawer had started and is unfinished
- ((looking-at org-drawer-regexp)
- (goto-char (1- (match-beginning 1)))
- (setq column (current-column)))
- ;; Else, nothing noticeable found: get indentation and go on.
- (t (setq column (org-get-indentation))))))
- ;; Now apply indentation and move cursor accordingly
- (goto-char pos)
- (if (<= (current-column) (current-indentation))
- (org-indent-line-to column)
- (save-excursion (org-indent-line-to column)))
- ;; Special polishing for properties, see `org-property-format'
- (setq column (current-column))
- (beginning-of-line 1)
- (if (looking-at org-property-re)
- (replace-match (concat (match-string 4)
- (format org-property-format
- (match-string 1) (match-string 3)))
- t t))
- (org-move-to-column column))))
+ (cond
+ (orgstruct-is-++
+ (let ((indent-line-function
+ (cl-cadadr (assq 'indent-line-function org-fb-vars))))
+ (indent-according-to-mode)))
+ ((org-at-heading-p) 'noindent)
+ (t
+ (let* ((element (save-excursion (beginning-of-line) (org-element-at-point)))
+ (type (org-element-type element)))
+ (cond ((and (memq type '(plain-list item))
+ (= (line-beginning-position)
+ (org-element-property :post-affiliated element)))
+ 'noindent)
+ ((and (eq type 'latex-environment)
+ (>= (point) (org-element-property :post-affiliated element))
+ (< (point) (org-with-wide-buffer
+ (goto-char (org-element-property :end element))
+ (skip-chars-backward " \r\t\n")
+ (line-beginning-position 2))))
+ 'noindent)
+ ((and (eq type 'src-block)
+ org-src-tab-acts-natively
+ (> (line-beginning-position)
+ (org-element-property :post-affiliated element))
+ (< (line-beginning-position)
+ (org-with-wide-buffer
+ (goto-char (org-element-property :end element))
+ (skip-chars-backward " \r\t\n")
+ (line-beginning-position))))
+ (org-babel-do-key-sequence-in-edit-buffer (kbd "TAB")))
+ (t
+ (let ((column (org--get-expected-indentation element nil)))
+ ;; Preserve current column.
+ (if (<= (current-column) (current-indentation))
+ (indent-line-to column)
+ (save-excursion (indent-line-to column))))
+ ;; Align node property. Also preserve current column.
+ (when (eq type 'node-property)
+ (let ((column (current-column)))
+ (org--align-node-property)
+ (org-move-to-column column)))))))))
+
+(defun org-indent-region (start end)
+ "Indent each non-blank line in the region.
+Called from a program, START and END specify the region to
+indent. The function will not indent contents of example blocks,
+verse blocks and export blocks as leading white spaces are
+assumed to be significant there."
+ (interactive "r")
+ (save-excursion
+ (goto-char start)
+ (skip-chars-forward " \r\t\n")
+ (unless (eobp) (beginning-of-line))
+ (let ((indent-to
+ (lambda (ind pos)
+ ;; Set IND as indentation for all lines between point and
+ ;; POS. Blank lines are ignored. Leave point after POS
+ ;; once done.
+ (let ((limit (copy-marker pos)))
+ (while (< (point) limit)
+ (unless (looking-at-p "[ \t]*$") (indent-line-to ind))
+ (forward-line))
+ (set-marker limit nil))))
+ (end (copy-marker end)))
+ (while (< (point) end)
+ (if (or (looking-at-p " \r\t\n") (org-at-heading-p)) (forward-line)
+ (let* ((element (org-element-at-point))
+ (type (org-element-type element))
+ (element-end (copy-marker (org-element-property :end element)))
+ (ind (org--get-expected-indentation element nil)))
+ (cond
+ ;; Element indented as a single block. Example blocks
+ ;; preserving indentation are a special case since the
+ ;; "contents" must not be indented whereas the block
+ ;; boundaries can.
+ ((or (memq type '(export-block latex-environment))
+ (and (eq type 'example-block)
+ (not
+ (or org-src-preserve-indentation
+ (org-element-property :preserve-indent element)))))
+ (let ((offset (- ind (org-get-indentation))))
+ (unless (zerop offset)
+ (indent-rigidly (org-element-property :begin element)
+ (org-element-property :end element)
+ offset)))
+ (goto-char element-end))
+ ;; Elements indented line wise. Be sure to exclude
+ ;; example blocks (preserving indentation) and source
+ ;; blocks from this category as they are treated
+ ;; specially later.
+ ((or (memq type '(paragraph table table-row))
+ (not (or (org-element-property :contents-begin element)
+ (memq type '(example-block src-block)))))
+ (when (eq type 'node-property)
+ (org--align-node-property)
+ (beginning-of-line))
+ (funcall indent-to ind (min element-end end)))
+ ;; Elements consisting of three parts: before the
+ ;; contents, the contents, and after the contents. The
+ ;; contents are treated specially, according to the
+ ;; element type, or not indented at all. Other parts are
+ ;; indented as a single block.
+ (t
+ (let* ((post (copy-marker
+ (org-element-property :post-affiliated element)))
+ (cbeg
+ (copy-marker
+ (cond
+ ((not (org-element-property :contents-begin element))
+ ;; Fake contents for source blocks.
+ (org-with-wide-buffer
+ (goto-char post)
+ (line-beginning-position 2)))
+ ((memq type '(footnote-definition item plain-list))
+ ;; Contents in these elements could start on
+ ;; the same line as the beginning of the
+ ;; element. Make sure we start indenting
+ ;; from the second line.
+ (org-with-wide-buffer
+ (goto-char post)
+ (end-of-line)
+ (skip-chars-forward " \r\t\n")
+ (if (eobp) (point) (line-beginning-position))))
+ (t (org-element-property :contents-begin element)))))
+ (cend (copy-marker
+ (or (org-element-property :contents-end element)
+ ;; Fake contents for source blocks.
+ (org-with-wide-buffer
+ (goto-char element-end)
+ (skip-chars-backward " \r\t\n")
+ (line-beginning-position)))
+ t)))
+ ;; Do not change items indentation individually as it
+ ;; might break the list as a whole. On the other
+ ;; hand, when at a plain list, indent it as a whole.
+ (cond ((eq type 'plain-list)
+ (let ((offset (- ind (org-get-indentation))))
+ (unless (zerop offset)
+ (indent-rigidly (org-element-property :begin element)
+ (org-element-property :end element)
+ offset))
+ (goto-char cbeg)))
+ ((eq type 'item) (goto-char cbeg))
+ (t (funcall indent-to ind (min cbeg end))))
+ (when (< (point) end)
+ (cl-case type
+ ((example-block verse-block))
+ (src-block
+ ;; In a source block, indent source code
+ ;; according to language major mode, but only if
+ ;; `org-src-tab-acts-natively' is non-nil.
+ (when (and (< (point) end) org-src-tab-acts-natively)
+ (ignore-errors
+ (org-babel-do-in-edit-buffer
+ (indent-region (point-min) (point-max))))))
+ (t (org-indent-region (point) (min cend end))))
+ (goto-char (min cend end))
+ (when (< (point) end)
+ (funcall indent-to ind (min element-end end))))
+ (set-marker post nil)
+ (set-marker cbeg nil)
+ (set-marker cend nil))))
+ (set-marker element-end nil))))
+ (set-marker end nil))))
(defun org-indent-drawer ()
"Indent the drawer at point."
(interactive)
- (let ((p (point))
- (e (and (save-excursion (re-search-forward ":END:" nil t))
- (match-end 0)))
- (folded
- (save-excursion
- (end-of-line)
- (when (overlays-at (point))
- (member 'invisible (overlay-properties
- (car (overlays-at (point)))))))))
- (when folded (org-cycle))
- (indent-for-tab-command)
- (while (and (move-beginning-of-line 2) (< (point) e))
- (indent-for-tab-command))
- (goto-char p)
- (when folded (org-cycle)))
+ (unless (save-excursion
+ (beginning-of-line)
+ (looking-at-p org-drawer-regexp))
+ (user-error "Not at a drawer"))
+ (let ((element (org-element-at-point)))
+ (unless (memq (org-element-type element) '(drawer property-drawer))
+ (user-error "Not at a drawer"))
+ (org-with-wide-buffer
+ (org-indent-region (org-element-property :begin element)
+ (org-element-property :end element))))
(message "Drawer at point indented"))
(defun org-indent-block ()
"Indent the block at point."
(interactive)
- (let ((p (point))
- (case-fold-search t)
- (e (and (save-excursion (re-search-forward "#\\+end_?\\(?:[a-z]+\\)?" nil t))
- (match-end 0)))
- (folded
- (save-excursion
- (end-of-line)
- (when (overlays-at (point))
- (member 'invisible (overlay-properties
- (car (overlays-at (point)))))))))
- (when folded (org-cycle))
- (indent-for-tab-command)
- (while (and (move-beginning-of-line 2) (< (point) e))
- (indent-for-tab-command))
- (goto-char p)
- (when folded (org-cycle)))
+ (unless (save-excursion
+ (beginning-of-line)
+ (let ((case-fold-search t))
+ (looking-at-p "[ \t]*#\\+\\(begin\\|end\\)_")))
+ (user-error "Not at a block"))
+ (let ((element (org-element-at-point)))
+ (unless (memq (org-element-type element)
+ '(comment-block center-block dynamic-block example-block
+ export-block quote-block special-block
+ src-block verse-block))
+ (user-error "Not at a block"))
+ (org-with-wide-buffer
+ (org-indent-region (org-element-property :begin element)
+ (org-element-property :end element))))
(message "Block at point indented"))
-(defun org-indent-region (start end)
- "Indent region."
- (interactive "r")
- (save-excursion
- (let ((line-end (org-current-line end)))
- (goto-char start)
- (while (< (org-current-line) line-end)
- (cond ((org-in-src-block-p t) (org-src-native-tab-command-maybe))
- (t (call-interactively 'org-indent-line)))
- (move-beginning-of-line 2)))))
-
;;; Filling
@@ -22294,20 +23107,20 @@ hierarchy of headlines by UP levels before marking the subtree."
(require 'org-element)
;; Prevent auto-fill from inserting unwanted new items.
(when (boundp 'fill-nobreak-predicate)
- (org-set-local
- 'fill-nobreak-predicate
+ (setq-local
+ fill-nobreak-predicate
(org-uniquify
(append fill-nobreak-predicate
'(org-fill-line-break-nobreak-p
org-fill-paragraph-with-timestamp-nobreak-p)))))
(let ((paragraph-ending (substring org-element-paragraph-separate 1)))
- (org-set-local 'paragraph-start paragraph-ending)
- (org-set-local 'paragraph-separate paragraph-ending))
- (org-set-local 'fill-paragraph-function 'org-fill-paragraph)
- (org-set-local 'auto-fill-inhibit-regexp nil)
- (org-set-local 'adaptive-fill-function 'org-adaptive-fill-function)
- (org-set-local 'normal-auto-fill-function 'org-auto-fill-function)
- (org-set-local 'comment-line-break-function 'org-comment-line-break-function))
+ (setq-local paragraph-start paragraph-ending)
+ (setq-local paragraph-separate paragraph-ending))
+ (setq-local fill-paragraph-function 'org-fill-paragraph)
+ (setq-local auto-fill-inhibit-regexp nil)
+ (setq-local adaptive-fill-function 'org-adaptive-fill-function)
+ (setq-local normal-auto-fill-function 'org-auto-fill-function)
+ (setq-local comment-line-break-function 'org-comment-line-break-function))
(defun org-fill-line-break-nobreak-p ()
"Non-nil when a new line at point would create an Org line break."
@@ -22332,69 +23145,64 @@ matches in paragraphs or comments, use it."
(when (derived-mode-p 'message-mode)
(save-excursion
(beginning-of-line)
- (cond ((or (not (message-in-body-p))
- (looking-at orgtbl-line-start-regexp))
- (throw 'exit nil))
+ (cond ((not (message-in-body-p)) (throw 'exit nil))
+ ((looking-at-p org-table-line-regexp) (throw 'exit nil))
((looking-at message-cite-prefix-regexp)
(throw 'exit (match-string-no-properties 0)))
((looking-at org-outline-regexp)
- (throw 'exit (make-string (length (match-string 0)) ? ))))))
+ (throw 'exit (make-string (length (match-string 0)) ?\s))))))
(org-with-wide-buffer
- (let* ((p (line-beginning-position))
- (element (save-excursion
- (beginning-of-line)
- (or (ignore-errors (org-element-at-point))
- (user-error "An element cannot be parsed line %d"
- (line-number-at-pos (point))))))
- (type (org-element-type element))
- (post-affiliated (org-element-property :post-affiliated element)))
- (unless (and post-affiliated (< p post-affiliated))
- (case type
- (comment
- (save-excursion
- (beginning-of-line)
- (looking-at "[ \t]*")
- (concat (match-string 0) "# ")))
- (footnote-definition "")
- ((item plain-list)
- (make-string (org-list-item-body-column
- (or post-affiliated
- (org-element-property :begin element)))
- ? ))
- (paragraph
- ;; Fill prefix is usually the same as the current line,
- ;; unless the paragraph is at the beginning of an item.
- (let ((parent (org-element-property :parent element)))
+ (unless (org-at-heading-p)
+ (let* ((p (line-beginning-position))
+ (element (save-excursion
+ (beginning-of-line)
+ (org-element-at-point)))
+ (type (org-element-type element))
+ (post-affiliated (org-element-property :post-affiliated element)))
+ (unless (< p post-affiliated)
+ (cl-case type
+ (comment
(save-excursion
(beginning-of-line)
- (cond ((eq (org-element-type parent) 'item)
- (make-string (org-list-item-body-column
- (org-element-property :begin parent))
- ? ))
- ((and adaptive-fill-regexp
- ;; Locally disable
- ;; `adaptive-fill-function' to let
- ;; `fill-context-prefix' handle
- ;; `adaptive-fill-regexp' variable.
- (let (adaptive-fill-function)
- (fill-context-prefix
- post-affiliated
- (org-element-property :end element)))))
- ((looking-at "[ \t]+") (match-string 0))
- (t "")))))
- (comment-block
- ;; Only fill contents if P is within block boundaries.
- (let* ((cbeg (save-excursion (goto-char post-affiliated)
- (forward-line)
- (point)))
- (cend (save-excursion
- (goto-char (org-element-property :end element))
- (skip-chars-backward " \r\t\n")
- (line-beginning-position))))
- (when (and (>= p cbeg) (< p cend))
- (if (save-excursion (beginning-of-line) (looking-at "[ \t]+"))
- (match-string 0)
- ""))))))))))
+ (looking-at "[ \t]*")
+ (concat (match-string 0) "# ")))
+ (footnote-definition "")
+ ((item plain-list)
+ (make-string (org-list-item-body-column post-affiliated) ?\s))
+ (paragraph
+ ;; Fill prefix is usually the same as the current line,
+ ;; unless the paragraph is at the beginning of an item.
+ (let ((parent (org-element-property :parent element)))
+ (save-excursion
+ (beginning-of-line)
+ (cond ((eq (org-element-type parent) 'item)
+ (make-string (org-list-item-body-column
+ (org-element-property :begin parent))
+ ?\s))
+ ((and adaptive-fill-regexp
+ ;; Locally disable
+ ;; `adaptive-fill-function' to let
+ ;; `fill-context-prefix' handle
+ ;; `adaptive-fill-regexp' variable.
+ (let (adaptive-fill-function)
+ (fill-context-prefix
+ post-affiliated
+ (org-element-property :end element)))))
+ ((looking-at "[ \t]+") (match-string 0))
+ (t "")))))
+ (comment-block
+ ;; Only fill contents if P is within block boundaries.
+ (let* ((cbeg (save-excursion (goto-char post-affiliated)
+ (forward-line)
+ (point)))
+ (cend (save-excursion
+ (goto-char (org-element-property :end element))
+ (skip-chars-backward " \r\t\n")
+ (line-beginning-position))))
+ (when (and (>= p cbeg) (< p cend))
+ (if (save-excursion (beginning-of-line) (looking-at "[ \t]+"))
+ (match-string 0)
+ "")))))))))))
(declare-function message-goto-body "message" ())
(defvar message-cite-prefix-regexp) ; From message.el
@@ -22420,11 +23228,11 @@ a footnote definition, try to fill the first paragraph within."
(looking-at message-cite-prefix-regexp))))
;; First ensure filling is correct in message-mode.
(let ((fill-paragraph-function
- (cadadr (assoc 'fill-paragraph-function org-fb-vars)))
- (fill-prefix (cadadr (assoc 'fill-prefix org-fb-vars)))
- (paragraph-start (cadadr (assoc 'paragraph-start org-fb-vars)))
+ (cl-cadadr (assq 'fill-paragraph-function org-fb-vars)))
+ (fill-prefix (cl-cadadr (assq 'fill-prefix org-fb-vars)))
+ (paragraph-start (cl-cadadr (assq 'paragraph-start org-fb-vars)))
(paragraph-separate
- (cadadr (assoc 'paragraph-separate org-fb-vars))))
+ (cl-cadadr (assq 'paragraph-separate org-fb-vars))))
(fill-paragraph nil))
(with-syntax-table org-mode-transpose-word-syntax-table
;; Move to end of line in order to get the first paragraph
@@ -22436,7 +23244,7 @@ a footnote definition, try to fill the first paragraph within."
(line-number-at-pos (point)))))))
;; First check if point is in a blank line at the beginning of
;; the buffer. In that case, ignore filling.
- (case (org-element-type element)
+ (cl-case (org-element-type element)
;; Use major mode filling function is src blocks.
(src-block (org-babel-do-key-sequence-in-edit-buffer (kbd "M-q")))
;; Align Org tables, leave table.el tables as-is.
@@ -22465,25 +23273,18 @@ a footnote definition, try to fill the first paragraph within."
(concat "^" message-cite-prefix-regexp) end t))
(setq end (match-beginning 0))))
;; Fill paragraph, taking line breaks into account.
- ;; For that, slice the paragraph using line breaks as
- ;; separators, and fill the parts in reverse order to
- ;; avoid messing with markers.
(save-excursion
- (goto-char end)
- (mapc
- (lambda (pos)
- (fill-region-as-paragraph pos (point) justify)
- (goto-char pos))
- ;; Find the list of ending positions for line breaks
- ;; in the current paragraph. Add paragraph
- ;; beginning to include first slice.
- (nreverse
- (cons beg
- (org-element-map
- (org-element--parse-objects
- beg end nil (org-element-restriction 'paragraph))
- 'line-break
- (lambda (lb) (org-element-property :end lb)))))))
+ (goto-char beg)
+ (let ((cuts (list beg)))
+ (while (re-search-forward "\\\\\\\\[ \t]*\n" end t)
+ (when (eq 'line-break
+ (org-element-type
+ (save-excursion (backward-char)
+ (org-element-context))))
+ (push (point) cuts)))
+ (dolist (c (delq end cuts))
+ (fill-region-as-paragraph c end justify)
+ (setq end c))))
t)))
;; Contents of `comment-block' type elements should be
;; filled as plain text, but only if point is within block
@@ -22564,6 +23365,130 @@ non-nil."
(insert-before-markers-and-inherit fill-prefix))
+;;; Fixed Width Areas
+
+(defun org-toggle-fixed-width ()
+ "Toggle fixed-width markup.
+
+Add or remove fixed-width markup on current line, whenever it
+makes sense. Return an error otherwise.
+
+If a region is active and if it contains only fixed-width areas
+or blank lines, remove all fixed-width markup in it. If the
+region contains anything else, convert all non-fixed-width lines
+to fixed-width ones.
+
+Blank lines at the end of the region are ignored unless the
+region only contains such lines."
+ (interactive)
+ (if (not (org-region-active-p))
+ ;; No region:
+ ;;
+ ;; Remove fixed width marker only in a fixed-with element.
+ ;;
+ ;; Add fixed width maker in paragraphs, in blank lines after
+ ;; elements or at the beginning of a headline or an inlinetask,
+ ;; and before any one-line elements (e.g., a clock).
+ (progn
+ (beginning-of-line)
+ (let* ((element (org-element-at-point))
+ (type (org-element-type element)))
+ (cond
+ ((and (eq type 'fixed-width)
+ (looking-at "[ \t]*\\(:\\(?: \\|$\\)\\)"))
+ (replace-match
+ "" nil nil nil (if (= (line-end-position) (match-end 0)) 0 1)))
+ ((and (memq type '(babel-call clock comment diary-sexp headline
+ horizontal-rule keyword paragraph
+ planning))
+ (<= (org-element-property :post-affiliated element) (point)))
+ (skip-chars-forward " \t")
+ (insert ": "))
+ ((and (looking-at-p "[ \t]*$")
+ (or (eq type 'inlinetask)
+ (save-excursion
+ (skip-chars-forward " \r\t\n")
+ (<= (org-element-property :end element) (point)))))
+ (delete-region (point) (line-end-position))
+ (org-indent-line)
+ (insert ": "))
+ (t (user-error "Cannot insert a fixed-width line here")))))
+ ;; Region active.
+ (let* ((begin (save-excursion
+ (goto-char (region-beginning))
+ (line-beginning-position)))
+ (end (copy-marker
+ (save-excursion
+ (goto-char (region-end))
+ (unless (eolp) (beginning-of-line))
+ (if (save-excursion (re-search-backward "\\S-" begin t))
+ (progn (skip-chars-backward " \r\t\n") (point))
+ (point)))))
+ (all-fixed-width-p
+ (catch 'not-all-p
+ (save-excursion
+ (goto-char begin)
+ (skip-chars-forward " \r\t\n")
+ (when (eobp) (throw 'not-all-p nil))
+ (while (< (point) end)
+ (let ((element (org-element-at-point)))
+ (if (eq (org-element-type element) 'fixed-width)
+ (goto-char (org-element-property :end element))
+ (throw 'not-all-p nil))))
+ t))))
+ (if all-fixed-width-p
+ (save-excursion
+ (goto-char begin)
+ (while (< (point) end)
+ (when (looking-at "[ \t]*\\(:\\(?: \\|$\\)\\)")
+ (replace-match
+ "" nil nil nil
+ (if (= (line-end-position) (match-end 0)) 0 1)))
+ (forward-line)))
+ (let ((min-ind (point-max)))
+ ;; Find minimum indentation across all lines.
+ (save-excursion
+ (goto-char begin)
+ (if (not (save-excursion (re-search-forward "\\S-" end t)))
+ (setq min-ind 0)
+ (catch 'zerop
+ (while (< (point) end)
+ (unless (looking-at-p "[ \t]*$")
+ (let ((ind (org-get-indentation)))
+ (setq min-ind (min min-ind ind))
+ (when (zerop ind) (throw 'zerop t))))
+ (forward-line)))))
+ ;; Loop over all lines and add fixed-width markup everywhere
+ ;; but in fixed-width lines.
+ (save-excursion
+ (goto-char begin)
+ (while (< (point) end)
+ (cond
+ ((org-at-heading-p)
+ (insert ": ")
+ (forward-line)
+ (while (and (< (point) end) (looking-at-p "[ \t]*$"))
+ (insert ":")
+ (forward-line)))
+ ((looking-at-p "[ \t]*:\\( \\|$\\)")
+ (let* ((element (org-element-at-point))
+ (element-end (org-element-property :end element)))
+ (if (eq (org-element-type element) 'fixed-width)
+ (progn (goto-char element-end)
+ (skip-chars-backward " \r\t\n")
+ (forward-line))
+ (let ((limit (min end element-end)))
+ (while (< (point) limit)
+ (org-move-to-column min-ind t)
+ (insert ": ")
+ (forward-line))))))
+ (t
+ (org-move-to-column min-ind t)
+ (insert ": ")
+ (forward-line)))))))
+ (set-marker end nil))))
+
+
;;; Comments
;; Org comments syntax is quite complex. It requires the entire line
@@ -22584,87 +23509,139 @@ non-nil."
(defun org-setup-comments-handling ()
(interactive)
- (org-set-local 'comment-use-syntax nil)
- (org-set-local 'comment-start "# ")
- (org-set-local 'comment-start-skip "^\\s-*#\\(?: \\|$\\)")
- (org-set-local 'comment-insert-comment-function 'org-insert-comment)
- (org-set-local 'comment-region-function 'org-comment-or-uncomment-region)
- (org-set-local 'uncomment-region-function 'org-comment-or-uncomment-region))
+ (setq-local comment-use-syntax nil)
+ (setq-local comment-start "# ")
+ (setq-local comment-start-skip "^\\s-*#\\(?: \\|$\\)")
+ (setq-local comment-insert-comment-function 'org-insert-comment)
+ (setq-local comment-region-function 'org-comment-or-uncomment-region)
+ (setq-local uncomment-region-function 'org-comment-or-uncomment-region))
(defun org-insert-comment ()
"Insert an empty comment above current line.
-If the line is empty, insert comment at its beginning."
- (beginning-of-line)
- (if (looking-at "\\s-*$") (replace-match "") (open-line 1))
- (org-indent-line)
- (insert "# "))
+If the line is empty, insert comment at its beginning. When
+point is within a source block, comment according to the related
+major mode."
+ (if (let ((element (org-element-at-point)))
+ (and (eq (org-element-type element) 'src-block)
+ (< (save-excursion
+ (goto-char (org-element-property :post-affiliated element))
+ (line-end-position))
+ (point))
+ (> (save-excursion
+ (goto-char (org-element-property :end element))
+ (skip-chars-backward " \r\t\n")
+ (line-beginning-position))
+ (point))))
+ (org-babel-do-in-edit-buffer (call-interactively 'comment-dwim))
+ (beginning-of-line)
+ (if (looking-at "\\s-*$") (delete-region (point) (point-at-eol))
+ (open-line 1))
+ (org-indent-line)
+ (insert "# ")))
(defvar comment-empty-lines) ; From newcomment.el.
(defun org-comment-or-uncomment-region (beg end &rest _)
"Comment or uncomment each non-blank line in the region.
Uncomment each non-blank line between BEG and END if it only
-contains commented lines. Otherwise, comment them."
- (save-restriction
- ;; Restrict region
- (narrow-to-region (save-excursion (goto-char beg)
- (skip-chars-forward " \r\t\n" end)
- (line-beginning-position))
- (save-excursion (goto-char end)
- (skip-chars-backward " \r\t\n" beg)
- (line-end-position)))
- (let ((uncommentp
- ;; UNCOMMENTP is non-nil when every non blank line between
- ;; BEG and END is a comment.
- (save-excursion
- (goto-char (point-min))
- (while (and (not (eobp))
- (let ((element (org-element-at-point)))
- (and (eq (org-element-type element) 'comment)
- (goto-char (min (point-max)
- (org-element-property
- :end element)))))))
- (eobp))))
- (if uncommentp
- ;; Only blank lines and comments in region: uncomment it.
- (save-excursion
- (goto-char (point-min))
- (while (not (eobp))
- (when (looking-at "[ \t]*\\(#\\(?: \\|$\\)\\)")
- (replace-match "" nil nil nil 1))
- (forward-line)))
- ;; Comment each line in region.
- (let ((min-indent (point-max)))
- ;; First find the minimum indentation across all lines.
- (save-excursion
- (goto-char (point-min))
- (while (and (not (eobp)) (not (zerop min-indent)))
- (unless (looking-at "[ \t]*$")
- (setq min-indent (min min-indent (current-indentation))))
- (forward-line)))
- ;; Then loop over all lines.
- (save-excursion
- (goto-char (point-min))
- (while (not (eobp))
- (unless (and (not comment-empty-lines) (looking-at "[ \t]*$"))
- ;; Don't get fooled by invisible text (e.g. link path)
- ;; when moving to column MIN-INDENT.
- (let ((buffer-invisibility-spec nil))
- (org-move-to-column min-indent t))
- (insert comment-start))
- (forward-line))))))))
+contains commented lines. Otherwise, comment them. If region is
+strictly within a source block, use appropriate comment syntax."
+ (if (let ((element (org-element-at-point)))
+ (and (eq (org-element-type element) 'src-block)
+ (< (save-excursion
+ (goto-char (org-element-property :post-affiliated element))
+ (line-end-position))
+ beg)
+ (>= (save-excursion
+ (goto-char (org-element-property :end element))
+ (skip-chars-backward " \r\t\n")
+ (line-beginning-position))
+ end)))
+ ;; Translate region boundaries for the Org buffer to the source
+ ;; buffer.
+ (let ((offset (- end beg)))
+ (save-excursion
+ (goto-char beg)
+ (org-babel-do-in-edit-buffer
+ (comment-or-uncomment-region (point) (+ offset (point))))))
+ (save-restriction
+ ;; Restrict region
+ (narrow-to-region (save-excursion (goto-char beg)
+ (skip-chars-forward " \r\t\n" end)
+ (line-beginning-position))
+ (save-excursion (goto-char end)
+ (skip-chars-backward " \r\t\n" beg)
+ (line-end-position)))
+ (let ((uncommentp
+ ;; UNCOMMENTP is non-nil when every non blank line between
+ ;; BEG and END is a comment.
+ (save-excursion
+ (goto-char (point-min))
+ (while (and (not (eobp))
+ (let ((element (org-element-at-point)))
+ (and (eq (org-element-type element) 'comment)
+ (goto-char (min (point-max)
+ (org-element-property
+ :end element)))))))
+ (eobp))))
+ (if uncommentp
+ ;; Only blank lines and comments in region: uncomment it.
+ (save-excursion
+ (goto-char (point-min))
+ (while (not (eobp))
+ (when (looking-at "[ \t]*\\(#\\(?: \\|$\\)\\)")
+ (replace-match "" nil nil nil 1))
+ (forward-line)))
+ ;; Comment each line in region.
+ (let ((min-indent (point-max)))
+ ;; First find the minimum indentation across all lines.
+ (save-excursion
+ (goto-char (point-min))
+ (while (and (not (eobp)) (not (zerop min-indent)))
+ (unless (looking-at "[ \t]*$")
+ (setq min-indent (min min-indent (current-indentation))))
+ (forward-line)))
+ ;; Then loop over all lines.
+ (save-excursion
+ (goto-char (point-min))
+ (while (not (eobp))
+ (unless (and (not comment-empty-lines) (looking-at "[ \t]*$"))
+ ;; Don't get fooled by invisible text (e.g. link path)
+ ;; when moving to column MIN-INDENT.
+ (let ((buffer-invisibility-spec nil))
+ (org-move-to-column min-indent t))
+ (insert comment-start))
+ (forward-line)))))))))
+
+(defun org-comment-dwim (_arg)
+ "Call `comment-dwim' within a source edit buffer if needed."
+ (interactive "*P")
+ (if (org-in-src-block-p)
+ (org-babel-do-in-edit-buffer (call-interactively 'comment-dwim))
+ (call-interactively 'comment-dwim)))
-;;; Planning
+;;; Timestamps API
;; This section contains tools to operate on timestamp objects, as
;; returned by, e.g. `org-element-context'.
+(defun org-timestamp--to-internal-time (timestamp &optional end)
+ "Encode TIMESTAMP object into Emacs internal time.
+Use end of date range or time range when END is non-nil."
+ (apply #'encode-time
+ (cons 0
+ (mapcar
+ (lambda (prop) (or (org-element-property prop timestamp) 0))
+ (if end '(:minute-end :hour-end :day-end :month-end :year-end)
+ '(:minute-start :hour-start :day-start :month-start
+ :year-start))))))
+
(defun org-timestamp-has-time-p (timestamp)
"Non-nil when TIMESTAMP has a time specified."
(org-element-property :hour-start timestamp))
-(defun org-timestamp-format (timestamp format &optional end zone)
- "Format a TIMESTAMP element into a string.
+(defun org-timestamp-format (timestamp format &optional end utc)
+ "Format a TIMESTAMP object into a string.
FORMAT is a format specifier to be passed to
`format-time-string'.
@@ -22672,33 +23649,22 @@ FORMAT is a format specifier to be passed to
When optional argument END is non-nil, use end of date-range or
time-range, if possible.
-The optional ZONE is omitted or nil for Emacs local time, t for
-Universal Time, `wall' for system wall clock time, or a string as
-in the TZ environment variable. It can also be a list (as from
-`current-time-zone') or an integer (as from `decode-time')
-applied without consideration for daylight saving time."
+When optional argument UTC is non-nil, time will be expressed as
+Universal Time."
(format-time-string
- format
- (apply 'encode-time
- (cons 0
- (mapcar
- (lambda (prop) (or (org-element-property prop timestamp) 0))
- (if end '(:minute-end :hour-end :day-end :month-end :year-end)
- '(:minute-start :hour-start :day-start :month-start
- :year-start)))))
- zone))
+ format (org-timestamp--to-internal-time timestamp end)
+ (and utc t)))
(defun org-timestamp-split-range (timestamp &optional end)
- "Extract a timestamp object from a date or time range.
+ "Extract a TIMESTAMP object from a date or time range.
-TIMESTAMP is a timestamp object. END, when non-nil, means extract
-the end of the range. Otherwise, extract its start.
+END, when non-nil, means extract the end of the range.
+Otherwise, extract its start.
-Return a new timestamp object sharing the same parent as
-TIMESTAMP."
+Return a new timestamp object."
(let ((type (org-element-property :type timestamp)))
(if (memq type '(active inactive diary)) timestamp
- (let ((split-ts (list 'timestamp (copy-sequence (nth 1 timestamp)))))
+ (let ((split-ts (org-element-copy timestamp)))
;; Set new type.
(org-element-put-property
split-ts :type (if (eq type 'active-range) 'active 'inactive))
@@ -22712,91 +23678,43 @@ TIMESTAMP."
(dolist (p-cell p-alist)
(org-element-put-property
split-ts
- (funcall (if end 'car 'cdr) p-cell)
+ (funcall (if end #'car #'cdr) p-cell)
(org-element-property
- (funcall (if end 'cdr 'car) p-cell) split-ts)))
+ (funcall (if end #'cdr #'car) p-cell) split-ts)))
;; Eventually refresh `:raw-value'.
(org-element-put-property split-ts :raw-value nil)
(org-element-put-property
split-ts :raw-value (org-element-interpret-data split-ts)))))))
(defun org-timestamp-translate (timestamp &optional boundary)
- "Apply `org-translate-time' on a TIMESTAMP object.
+ "Translate TIMESTAMP object to custom format.
+
+Format string is defined in `org-time-stamp-custom-formats',
+which see.
+
When optional argument BOUNDARY is non-nil, it is either the
symbol `start' or `end'. In this case, only translate the
starting or ending part of TIMESTAMP if it is a date or time
-range. Otherwise, translate both parts."
- (if (and (not boundary)
- (memq (org-element-property :type timestamp)
- '(active-range inactive-range)))
- (concat
- (org-translate-time
- (org-element-property :raw-value
- (org-timestamp-split-range timestamp)))
- "--"
- (org-translate-time
- (org-element-property :raw-value
- (org-timestamp-split-range timestamp t))))
- (org-translate-time
- (org-element-property
- :raw-value
- (if (not boundary) timestamp
- (org-timestamp-split-range timestamp (eq boundary 'end)))))))
+range. Otherwise, translate both parts.
+Return timestamp as-is if `org-display-custom-times' is nil or if
+it has a `diary' type."
+ (let ((type (org-element-property :type timestamp)))
+ (if (or (not org-display-custom-times) (eq type 'diary))
+ (org-element-interpret-data timestamp)
+ (let ((fmt (funcall (if (org-timestamp-has-time-p timestamp) #'cdr #'car)
+ org-time-stamp-custom-formats)))
+ (if (and (not boundary) (memq type '(active-range inactive-range)))
+ (concat (org-timestamp-format timestamp fmt)
+ "--"
+ (org-timestamp-format timestamp fmt t))
+ (org-timestamp-format timestamp fmt (eq boundary 'end)))))))
-;;; Other stuff.
-(defun org-toggle-fixed-width-section (arg)
- "Toggle the fixed-width export.
-If there is no active region, the QUOTE keyword at the current headline is
-inserted or removed. When present, it causes the text between this headline
-and the next to be exported as fixed-width text, and unmodified.
-If there is an active region, this command adds or removes a colon as the
-first character of this line. If the first character of a line is a colon,
-this line is also exported in fixed-width font."
- (interactive "P")
- (let* ((cc 0)
- (regionp (org-region-active-p))
- (beg (if regionp (region-beginning) (point)))
- (end (if regionp (region-end)))
- (nlines (or arg (if (and beg end) (count-lines beg end) 1)))
- (case-fold-search nil)
- (re "[ \t]*\\(:\\(?: \\|$\\)\\)")
- off)
- (if regionp
- (save-excursion
- (goto-char beg)
- (setq cc (current-column))
- (beginning-of-line 1)
- (setq off (looking-at re))
- (while (> nlines 0)
- (setq nlines (1- nlines))
- (beginning-of-line 1)
- (cond
- (arg
- (org-move-to-column cc t)
- (insert ": \n")
- (forward-line -1))
- ((and off (looking-at re))
- (replace-match "" t t nil 1))
- ((not off) (org-move-to-column cc t) (insert ": ")))
- (forward-line 1)))
- (save-excursion
- (org-back-to-heading)
- (cond
- ((looking-at (format org-heading-keyword-regexp-format
- org-quote-string))
- (goto-char (match-end 1))
- (looking-at (concat " +" org-quote-string))
- (replace-match "" t t)
- (when (eolp) (insert " ")))
- ((looking-at org-outline-regexp)
- (goto-char (match-end 0))
- (insert org-quote-string " ")))))))
+;;; Other stuff.
(defvar reftex-docstruct-symbol)
-(defvar reftex-cite-format)
(defvar org--rds)
(defun org-reftex-citation ()
@@ -22814,131 +23732,137 @@ Export of such citations to both LaTeX and HTML is handled by the contributed
package ox-bibtex by Taru Karttunen."
(interactive)
(let ((reftex-docstruct-symbol 'org--rds)
- (reftex-cite-format "\\cite{%l}")
org--rds bib)
- (save-excursion
- (save-restriction
- (widen)
- (let ((case-fold-search t)
- (re "^#\\+bibliography:[ \t]+\\([^ \t\n]+\\)"))
- (if (not (save-excursion
- (or (re-search-forward re nil t)
- (re-search-backward re nil t))))
- (error "No bibliography defined in file")
- (setq bib (concat (match-string 1) ".bib")
- org--rds (list (list 'bib bib)))))))
+ (org-with-wide-buffer
+ (let ((case-fold-search t)
+ (re "^[ \t]*#\\+BIBLIOGRAPHY:[ \t]+\\([^ \t\n]+\\)"))
+ (if (not (save-excursion
+ (or (re-search-forward re nil t)
+ (re-search-backward re nil t))))
+ (user-error "No bibliography defined in file")
+ (setq bib (concat (match-string 1) ".bib")
+ org--rds (list (list 'bib bib))))))
(call-interactively 'reftex-citation)))
;;;; Functions extending outline functionality
-(defun org-beginning-of-line (&optional arg)
- "Go to the beginning of the current line. If that is invisible, continue
-to a visible line beginning. This makes the function of C-a more intuitive.
-If this is a headline, and `org-special-ctrl-a/e' is set, ignore tags on the
-first attempt, and only move to after the tags when the cursor is already
-beyond the end of the headline."
- (interactive "P")
- (let ((pos (point))
- (special (if (consp org-special-ctrl-a/e)
- (car org-special-ctrl-a/e)
- org-special-ctrl-a/e))
- deactivate-mark refpos)
- (if (org-bound-and-true-p visual-line-mode)
- (beginning-of-visual-line 1)
- (beginning-of-line 1))
- (if (and arg (fboundp 'move-beginning-of-line))
- (call-interactively 'move-beginning-of-line)
- (if (bobp)
- nil
- (backward-char 1)
- (if (org-truely-invisible-p)
- (while (and (not (bobp)) (org-truely-invisible-p))
- (backward-char 1)
- (beginning-of-line 1))
- (forward-char 1))))
- (when special
- (cond
- ((and (looking-at org-complex-heading-regexp)
- (= (char-after (match-end 1)) ?\ ))
- (setq refpos (min (1+ (or (match-end 3) (match-end 2) (match-end 1)))
- (point-at-eol)))
- (goto-char
- (if (eq special t)
- (cond ((> pos refpos) refpos)
- ((= pos (point)) refpos)
- (t (point)))
- (cond ((> pos (point)) (point))
- ((not (eq last-command this-command)) (point))
- (t refpos)))))
- ((org-at-item-p)
- ;; Being at an item and not looking at an the item means point
- ;; was previously moved to beginning of a visual line, which
- ;; doesn't contain the item. Therefore, do nothing special,
- ;; just stay here.
- (when (looking-at org-list-full-item-re)
- ;; Set special position at first white space character after
- ;; bullet, and check-box, if any.
- (let ((after-bullet
- (let ((box (match-end 3)))
- (if (not box) (match-end 1)
- (let ((after (char-after box)))
- (if (and after (= after ? )) (1+ box) box))))))
- ;; Special case: Move point to special position when
- ;; currently after it or at beginning of line.
- (if (eq special t)
- (when (or (> pos after-bullet) (= (point) pos))
- (goto-char after-bullet))
- ;; Reversed case: Move point to special position when
- ;; point was already at beginning of line and command is
- ;; repeated.
- (when (and (= (point) pos) (eq last-command this-command))
- (goto-char after-bullet))))))))
- (org-no-warnings
- (and (featurep 'xemacs) (setq zmacs-region-stays t))))
- (setq disable-point-adjustment
- (or (not (invisible-p (point)))
- (not (invisible-p (max (point-min) (1- (point))))))))
-
-(defun org-end-of-line (&optional arg)
- "Go to the end of the line.
+(defun org-beginning-of-line (&optional n)
+ "Go to the beginning of the current visible line.
+
If this is a headline, and `org-special-ctrl-a/e' is set, ignore
tags on the first attempt, and only move to after the tags when
-the cursor is already beyond the end of the headline."
- (interactive "P")
- (let ((special (if (consp org-special-ctrl-a/e) (cdr org-special-ctrl-a/e)
- org-special-ctrl-a/e))
- (move-fun (cond ((org-bound-and-true-p visual-line-mode)
- 'end-of-visual-line)
- ((fboundp 'move-end-of-line) 'move-end-of-line)
- (t 'end-of-line)))
+the cursor is already beyond the end of the headline.
+
+With argument N not nil or 1, move forward N - 1 lines first."
+ (interactive "^p")
+ (let ((origin (point))
+ (special (pcase org-special-ctrl-a/e
+ (`(,C-a . ,_) C-a) (_ org-special-ctrl-a/e)))
deactivate-mark)
- (if (or (not special) arg) (call-interactively move-fun)
- (let* ((element (save-excursion (beginning-of-line)
- (org-element-at-point)))
- (type (org-element-type element)))
- (cond
- ((memq type '(headline inlinetask))
- (let ((pos (point)))
- (beginning-of-line 1)
- (if (looking-at (org-re ".*?\\(?:\\([ \t]*\\)\\(:[[:alnum:]_@#%:]+:\\)?[ \t]*\\)?$"))
- (if (eq special t)
- (if (or (< pos (match-beginning 1)) (= pos (match-end 0)))
- (goto-char (match-beginning 1))
- (goto-char (match-end 0)))
- (if (or (< pos (match-end 0))
- (not (eq this-command last-command)))
- (goto-char (match-end 0))
- (goto-char (match-beginning 1))))
- (call-interactively move-fun))))
- ((org-element-property :hiddenp element)
- ;; If element is hidden, `move-end-of-line' would put point
- ;; after it. Use `end-of-line' to stay on current line.
- (call-interactively 'end-of-line))
- (t (call-interactively move-fun)))))
- (org-no-warnings (and (featurep 'xemacs) (setq zmacs-region-stays t))))
- (setq disable-point-adjustment
- (or (not (invisible-p (point)))
- (not (invisible-p (max (point-min) (1- (point))))))))
+ ;; First move to a visible line.
+ (if (bound-and-true-p visual-line-mode)
+ (beginning-of-visual-line n)
+ (move-beginning-of-line n)
+ ;; `move-beginning-of-line' may leave point after invisible
+ ;; characters if line starts with such of these (e.g., with
+ ;; a link at column 0). Really move to the beginning of the
+ ;; current visible line.
+ (beginning-of-line))
+ (cond
+ ;; No special behavior. Point is already at the beginning of
+ ;; a line, logical or visual.
+ ((not special))
+ ;; `beginning-of-visual-line' left point before logical beginning
+ ;; of line: point is at the beginning of a visual line. Bail
+ ;; out.
+ ((and (bound-and-true-p visual-line-mode) (not (bolp))))
+ ((let ((case-fold-search nil)) (looking-at org-complex-heading-regexp))
+ ;; At a headline, special position is before the title, but
+ ;; after any TODO keyword or priority cookie.
+ (let ((refpos (min (1+ (or (match-end 3) (match-end 2) (match-end 1)))
+ (line-end-position)))
+ (bol (point)))
+ (if (eq special 'reversed)
+ (when (and (= origin bol) (eq last-command this-command))
+ (goto-char refpos))
+ (when (or (> origin refpos) (= origin bol))
+ (goto-char refpos)))))
+ ((and (looking-at org-list-full-item-re)
+ (memq (org-element-type (save-match-data (org-element-at-point)))
+ '(item plain-list)))
+ ;; Set special position at first white space character after
+ ;; bullet, and check-box, if any.
+ (let ((after-bullet
+ (let ((box (match-end 3)))
+ (cond ((not box) (match-end 1))
+ ((eq (char-after box) ?\s) (1+ box))
+ (t box)))))
+ (if (eq special 'reversed)
+ (when (and (= (point) origin) (eq last-command this-command))
+ (goto-char after-bullet))
+ (when (or (> origin after-bullet) (= (point) origin))
+ (goto-char after-bullet)))))
+ ;; No special context. Point is already at beginning of line.
+ (t nil))))
+
+(defun org-end-of-line (&optional n)
+ "Go to the end of the line, but before ellipsis, if any.
+
+If this is a headline, and `org-special-ctrl-a/e' is set, ignore
+tags on the first attempt, and only move to after the tags when
+the cursor is already beyond the end of the headline.
+
+With argument N not nil or 1, move forward N - 1 lines first."
+ (interactive "^p")
+ (let ((origin (point))
+ (special (pcase org-special-ctrl-a/e
+ (`(,_ . ,C-e) C-e) (_ org-special-ctrl-a/e)))
+ deactivate-mark)
+ ;; First move to a visible line.
+ (if (bound-and-true-p visual-line-mode)
+ (beginning-of-visual-line n)
+ (move-beginning-of-line n))
+ (cond
+ ;; At a headline, with tags.
+ ((and special
+ (save-excursion
+ (beginning-of-line)
+ (let ((case-fold-search nil))
+ (looking-at org-complex-heading-regexp)))
+ (match-end 5))
+ (let ((tags (save-excursion
+ (goto-char (match-beginning 5))
+ (skip-chars-backward " \t")
+ (point)))
+ (visual-end (and (bound-and-true-p visual-line-mode)
+ (save-excursion
+ (end-of-visual-line)
+ (point)))))
+ ;; If `end-of-visual-line' brings us before end of line or
+ ;; even tags, i.e., the headline spans over multiple visual
+ ;; lines, move there.
+ (cond ((and visual-end
+ (< visual-end tags)
+ (<= origin visual-end))
+ (goto-char visual-end))
+ ((eq special 'reversed)
+ (if (and (= origin (line-end-position))
+ (eq this-command last-command))
+ (goto-char tags)
+ (end-of-line)))
+ (t
+ (if (or (< origin tags) (= origin (line-end-position)))
+ (goto-char tags)
+ (end-of-line))))))
+ ((bound-and-true-p visual-line-mode)
+ (let ((bol (line-beginning-position)))
+ (end-of-visual-line)
+ ;; If `end-of-visual-line' gets us past the ellipsis at the
+ ;; end of a line, backtrack and use `end-of-line' instead.
+ (when (/= bol (line-beginning-position))
+ (goto-char bol)
+ (end-of-line))))
+ (t (end-of-line)))))
(define-key org-mode-map "\C-a" 'org-beginning-of-line)
(define-key org-mode-map "\C-e" 'org-end-of-line)
@@ -22948,18 +23872,43 @@ the cursor is already beyond the end of the headline."
This will call `backward-sentence' or `org-table-beginning-of-field',
depending on context."
(interactive)
- (cond
- ((org-at-table-p) (call-interactively 'org-table-beginning-of-field))
- (t (call-interactively 'backward-sentence))))
+ (let* ((element (org-element-at-point))
+ (contents-begin (org-element-property :contents-begin element))
+ (table (org-element-lineage element '(table) t)))
+ (if (and table
+ (> (point) contents-begin)
+ (<= (point) (org-element-property :contents-end table)))
+ (call-interactively #'org-table-beginning-of-field)
+ (save-restriction
+ (when (and contents-begin
+ (< (point-min) contents-begin)
+ (> (point) contents-begin))
+ (narrow-to-region contents-begin
+ (org-element-property :contents-end element)))
+ (call-interactively #'backward-sentence)))))
(defun org-forward-sentence (&optional _arg)
"Go to end of sentence, or end of table field.
This will call `forward-sentence' or `org-table-end-of-field',
depending on context."
(interactive)
- (cond
- ((org-at-table-p) (call-interactively 'org-table-end-of-field))
- (t (call-interactively 'forward-sentence))))
+ (let* ((element (org-element-at-point))
+ (contents-end (org-element-property :contents-end element))
+ (table (org-element-lineage element '(table) t)))
+ (if (and table
+ (>= (point) (org-element-property :contents-begin table))
+ (< (point) contents-end))
+ (call-interactively #'org-table-end-of-field)
+ (save-restriction
+ (when (and contents-end
+ (> (point-max) contents-end)
+ ;; Skip blank lines between elements.
+ (< (org-element-property :end element)
+ (save-excursion (goto-char contents-end)
+ (skip-chars-forward " \r\t\n"))))
+ (narrow-to-region (org-element-property :contents-begin element)
+ contents-end))
+ (call-interactively #'forward-sentence)))))
(define-key org-mode-map "\M-a" 'org-backward-sentence)
(define-key org-mode-map "\M-e" 'org-forward-sentence)
@@ -22971,14 +23920,14 @@ depending on context."
((or (not org-special-ctrl-k)
(bolp)
(not (org-at-heading-p)))
- (if (and (get-char-property (min (point-max) (point-at-eol)) 'invisible)
- org-ctrl-k-protect-subtree)
- (if (or (eq org-ctrl-k-protect-subtree 'error)
- (not (y-or-n-p "Kill hidden subtree along with headline? ")))
- (user-error "C-k aborted as it would kill a hidden subtree")))
+ (when (and (get-char-property (min (point-max) (point-at-eol)) 'invisible)
+ org-ctrl-k-protect-subtree
+ (or (eq org-ctrl-k-protect-subtree 'error)
+ (not (y-or-n-p "Kill hidden subtree along with headline? "))))
+ (user-error "C-k aborted as it would kill a hidden subtree"))
(call-interactively
- (if (org-bound-and-true-p visual-line-mode) 'kill-visual-line 'kill-line)))
- ((looking-at (org-re ".*?\\S-\\([ \t]+\\(:[[:alnum:]_@#%:]+:\\)\\)[ \t]*$"))
+ (if (bound-and-true-p visual-line-mode) 'kill-visual-line 'kill-line)))
+ ((looking-at ".*?\\S-\\([ \t]+\\(:[[:alnum:]_@#%:]+:\\)\\)[ \t]*$")
(kill-region (point) (match-beginning 1))
(org-set-tags nil t))
(t (kill-region (point) (point-at-eol)))))
@@ -22991,24 +23940,25 @@ This command will look at the current kill and check if is a single
subtree, or a series of subtrees[1]. If it passes the test, and if the
cursor is at the beginning of a line or after the stars of a currently
empty headline, then the yank is handled specially. How exactly depends
-on the value of the following variables, both set by default.
+on the value of the following variables.
-org-yank-folded-subtrees
- When set, the subtree(s) will be folded after insertion, but only
- if doing so would now swallow text after the yanked text.
+`org-yank-folded-subtrees'
+ By default, this variable is non-nil, which results in
+ subtree(s) being folded after insertion, except if doing so
+ would swallow text after the yanked text.
-org-yank-adjusted-subtrees
- When set, the subtree will be promoted or demoted in order to
- fit into the local outline tree structure, which means that the level
- will be adjusted so that it becomes the smaller one of the two
- *visible* surrounding headings.
+`org-yank-adjusted-subtrees'
+ When non-nil (the default value is nil), the subtree will be
+ promoted or demoted in order to fit into the local outline tree
+ structure, which means that the level will be adjusted so that it
+ becomes the smaller one of the two *visible* surrounding headings.
Any prefix to this command will cause `yank' to be called directly with
-no special treatment. In particular, a simple \\[universal-argument] prefix \
+no special treatment. In particular, a simple `\\[universal-argument]' prefix \
will just
plainly yank the text as it is.
-[1] The test checks if the first non-white line is a heading
+\[1] The test checks if the first non-white line is a heading
and if there are no other headings with fewer stars."
(interactive "P")
(org-yank-generic 'yank arg))
@@ -23051,7 +24001,7 @@ interactive command with similar behavior."
(or (looking-at org-outline-regexp)
(re-search-forward org-outline-regexp-bol end t))
(while (and (< (point) end) (looking-at org-outline-regexp))
- (hide-subtree)
+ (outline-hide-subtree)
(org-cycle-show-empty-lines 'folded)
(condition-case nil
(outline-forward-same-level 1)
@@ -23082,11 +24032,9 @@ interactive command with similar behavior."
(setq level (org-outline-level)))
(goto-char end)
(skip-chars-forward " \t\r\n\v\f")
- (if (or (eobp)
- (and (bolp) (looking-at org-outline-regexp)
- (<= (org-outline-level) level)))
- nil ; Nothing would be swallowed
- t))))) ; something would swallow
+ (not (or (eobp)
+ (and (bolp) (looking-at-p org-outline-regexp)
+ (<= (org-outline-level) level))))))))
(define-key org-mode-map "\C-y" 'org-yank)
@@ -23094,17 +24042,18 @@ interactive command with similar behavior."
"Check if point is at a character currently not visible.
This version does not only check the character property, but also
`visible-mode'."
- ;; Early versions of noutline don't have `outline-invisible-p'.
- (if (org-bound-and-true-p visible-mode)
- nil
- (outline-invisible-p)))
+ (unless (bound-and-true-p visible-mode)
+ (org-invisible-p)))
(defun org-invisible-p2 ()
- "Check if point is at a character currently not visible."
+ "Check if point is at a character currently not visible.
+
+If the point is at EOL (and not at the beginning of a buffer too),
+move it back by one char before doing this check."
(save-excursion
- (if (and (eolp) (not (bobp))) (backward-char 1))
- ;; Early versions of noutline don't have `outline-invisible-p'.
- (outline-invisible-p)))
+ (when (and (eolp) (not (bobp)))
+ (backward-char 1))
+ (org-invisible-p)))
(defun org-back-to-heading (&optional invisible-ok)
"Call `outline-back-to-heading', but provide a better error message."
@@ -23121,14 +24070,28 @@ This version does not only check the character property, but also
(defun org-at-heading-p (&optional ignored)
(outline-on-heading-p t))
-;; Compatibility alias with Org versions < 7.8.03
-(defalias 'org-on-heading-p 'org-at-heading-p)
+
+(defun org-in-commented-heading-p (&optional no-inheritance)
+ "Non-nil if point is under a commented heading.
+This function also checks ancestors of the current headline,
+unless optional argument NO-INHERITANCE is non-nil."
+ (cond
+ ((org-before-first-heading-p) nil)
+ ((let ((headline (nth 4 (org-heading-components))))
+ (and headline
+ (let ((case-fold-search nil))
+ (string-match-p (concat "^" org-comment-string "\\(?: \\|$\\)")
+ headline)))))
+ (no-inheritance nil)
+ (t
+ (save-excursion (and (org-up-heading-safe) (org-in-commented-heading-p))))))
(defun org-at-comment-p nil
- "Is cursor in a line starting with a # character?"
+ "Is cursor in a commented line?"
(save-excursion
- (beginning-of-line)
- (looking-at "^#")))
+ (save-match-data
+ (beginning-of-line)
+ (looking-at "^[ \t]*# "))))
(defun org-at-drawer-p nil
"Is cursor at a drawer keyword?"
@@ -23146,13 +24109,13 @@ This version does not only check the character property, but also
"If point is at the end of an empty headline, return t, else nil.
If the heading only contains a TODO keyword, it is still still considered
empty."
- (and (looking-at "[ \t]*$")
- (when org-todo-line-regexp
+ (let ((case-fold-search nil))
+ (and (looking-at "[ \t]*$")
+ org-todo-line-regexp
(save-excursion
- (beginning-of-line 1)
- (let ((case-fold-search nil))
- (looking-at org-todo-line-regexp)
- (string= (match-string 3) ""))))))
+ (beginning-of-line)
+ (looking-at org-todo-line-regexp)
+ (string= (match-string 3) "")))))
(defun org-at-heading-or-item-p ()
(or (org-at-heading-p) (org-at-item-p)))
@@ -23167,9 +24130,7 @@ empty."
"Move to the heading line of which the present line is a subheading.
This function considers both visible and invisible heading lines.
With argument, move up ARG levels."
- (if (fboundp 'outline-up-heading-all)
- (outline-up-heading-all arg) ; emacs 21 version of outline.el
- (outline-up-heading arg t))) ; emacs 22 version of outline.el
+ (outline-up-heading arg t))
(defun org-up-heading-safe ()
"Move to the heading line of which the present line is a subheading.
@@ -23179,14 +24140,11 @@ headline found, or nil if no higher level is found.
Also, this function will be a lot faster than `outline-up-heading',
because it relies on stars being the outline starters. This can really
make a significant difference in outlines with very many siblings."
- (let (start-level re)
- (org-back-to-heading t)
- (setq start-level (funcall outline-level))
- (if (equal start-level 1)
- nil
- (setq re (concat "^\\*\\{1," (number-to-string (1- start-level)) "\\} "))
- (if (re-search-backward re nil t)
- (funcall outline-level)))))
+ (when (ignore-errors (org-back-to-heading t))
+ (let ((level-up (1- (funcall outline-level))))
+ (and (> level-up 0)
+ (re-search-backward (format "^\\*\\{1,%d\\} " level-up) nil t)
+ (funcall outline-level)))))
(defun org-first-sibling-p ()
"Is this heading the first child of its parents?"
@@ -23211,7 +24169,7 @@ move point."
(pos (point))
(re org-outline-regexp-bol)
level l)
- (when (condition-case nil (org-back-to-heading t) (error nil))
+ (when (ignore-errors (org-back-to-heading t))
(setq level (funcall outline-level))
(catch 'exit
(or previous (forward-char 1))
@@ -23235,7 +24193,7 @@ move point."
Return t when a child was found. Otherwise don't move point and
return nil."
(let (level (pos (point)) (re org-outline-regexp-bol))
- (when (condition-case nil (org-back-to-heading t) (error nil))
+ (when (ignore-errors (org-back-to-heading t))
(setq level (outline-level))
(forward-char 1)
(if (and (re-search-forward re nil t) (> (outline-level) level))
@@ -23271,8 +24229,7 @@ This is like outline-next-sibling, but invisible headings are ok."
(outline-next-heading)
(while (and (not (eobp)) (> (funcall outline-level) level))
(outline-next-heading))
- (if (or (eobp) (< (funcall outline-level) level))
- nil
+ (unless (or (eobp) (< (funcall outline-level) level))
(point))))
(defun org-get-last-sibling ()
@@ -23285,8 +24242,7 @@ If there is no such heading, return nil."
(while (and (> (funcall outline-level) level)
(not (bobp)))
(outline-previous-heading))
- (if (< (funcall outline-level) level)
- nil
+ (unless (< (funcall outline-level) level)
(point)))))
(defun org-end-of-subtree (&optional invisible-ok to-heading)
@@ -23302,7 +24258,7 @@ If there is no such heading, return nil."
(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-mode
+ ;; 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.
@@ -23315,33 +24271,36 @@ If there is no such heading, return nil."
(setq first nil)
(outline-next-heading)))
(unless to-heading
- (if (memq (preceding-char) '(?\n ?\^M))
- (progn
- ;; Go to end of line before heading
- (forward-char -1)
- (if (memq (preceding-char) '(?\n ?\^M))
- ;; leave blank line before heading
- (forward-char -1))))))
+ (when (memq (preceding-char) '(?\n ?\^M))
+ ;; Go to end of line before heading
+ (forward-char -1)
+ (when (memq (preceding-char) '(?\n ?\^M))
+ ;; leave blank line before heading
+ (forward-char -1)))))
(point))
-(defun org-end-of-meta-data-and-drawers ()
- "Jump to the first text after meta data and drawers in the current entry.
-This will move over empty lines, lines with planning time stamps,
-clocking lines, and drawers."
+(defun org-end-of-meta-data (&optional full)
+ "Skip planning line and properties drawer in current entry.
+When optional argument FULL is non-nil, also skip empty lines,
+clocking lines and regular drawers at the beginning of the
+entry."
(org-back-to-heading t)
- (let ((end (save-excursion (outline-next-heading) (point)))
- (re (concat "\\(" org-drawer-regexp "\\)"
- "\\|" "[ \t]*" org-keyword-time-regexp)))
- (forward-line 1)
- (while (re-search-forward re end t)
- (if (not (match-end 1))
- ;; empty or planning line
- (forward-line 1)
- ;; a drawer, find the end
- (re-search-forward "^[ \t]*:END:" end 'move)
- (forward-line 1)))
- (and (re-search-forward "[^\n]" nil t) (backward-char 1))
- (point)))
+ (forward-line)
+ (when (looking-at-p org-planning-line-re) (forward-line))
+ (when (looking-at org-property-drawer-re)
+ (goto-char (match-end 0))
+ (forward-line))
+ (when (and full (not (org-at-heading-p)))
+ (catch 'exit
+ (let ((end (save-excursion (outline-next-heading) (point)))
+ (re (concat "[ \t]*$" "\\|" org-clock-line-re)))
+ (while (not (eobp))
+ (cond ((looking-at-p org-drawer-regexp)
+ (if (re-search-forward "^[ \t]*:END:[ \t]*$" end t)
+ (forward-line)
+ (throw 'exit t)))
+ ((looking-at-p re) (forward-line))
+ (t (throw 'exit t))))))))
(defun org-forward-heading-same-level (arg &optional invisible-ok)
"Move forward to the ARG'th subheading at same level as this one.
@@ -23349,32 +24308,27 @@ Stop at the first and last subheadings of a superior heading.
Normally this only looks at visible headings, but when INVISIBLE-OK is
non-nil it will also look at invisible ones."
(interactive "p")
- (if (not (ignore-errors (org-back-to-heading invisible-ok)))
- (if (and arg (< arg 0))
- (goto-char (point-min))
- (outline-next-heading))
- (org-at-heading-p)
- (let ((level (- (match-end 0) (match-beginning 0) 1))
- (f (if (and arg (< arg 0))
- 're-search-backward
- 're-search-forward))
- (count (if arg (abs arg) 1))
- (result (point)))
- (while (and (prog1 (> count 0)
- (forward-char (if (and arg (< arg 0)) -1 1)))
- (funcall f org-outline-regexp-bol nil 'move))
- (let ((l (- (match-end 0) (match-beginning 0) 1)))
- (cond ((< l level) (setq count 0))
- ((and (= l level)
- (or invisible-ok
- (progn
- (goto-char (line-beginning-position))
- (not (outline-invisible-p)))))
- (setq count (1- count))
- (when (eq l level)
- (setq result (point)))))))
- (goto-char result))
- (beginning-of-line 1)))
+ (let ((backward? (and arg (< arg 0))))
+ (if (org-before-first-heading-p)
+ (if backward? (goto-char (point-min)) (outline-next-heading))
+ (org-back-to-heading invisible-ok)
+ (unless backward? (end-of-line)) ;do not match current headline
+ (let ((level (- (match-end 0) (match-beginning 0) 1))
+ (f (if backward? #'re-search-backward #'re-search-forward))
+ (count (if arg (abs arg) 1))
+ (result (point)))
+ (while (and (> count 0)
+ (funcall f org-outline-regexp-bol nil 'move))
+ (let ((l (- (match-end 0) (match-beginning 0) 1)))
+ (cond ((< l level) (setq count 0))
+ ((and (= l level)
+ (or invisible-ok
+ (not (org-invisible-p
+ (line-beginning-position)))))
+ (cl-decf count)
+ (when (= l level) (setq result (point)))))))
+ (goto-char result))
+ (beginning-of-line))))
(defun org-backward-heading-same-level (arg &optional invisible-ok)
"Move backward to the ARG'th subheading at same level as this one.
@@ -23382,20 +24336,64 @@ Stop at the first and last subheadings of a superior heading."
(interactive "p")
(org-forward-heading-same-level (if arg (- arg) -1) invisible-ok))
+(defun org-next-visible-heading (arg)
+ "Move to the next visible heading.
+
+This function wraps `outline-next-visible-heading' with
+`org-with-limited-levels' in order to skip over inline tasks and
+respect customization of `org-odd-levels-only'."
+ (interactive "p")
+ (org-with-limited-levels
+ (outline-next-visible-heading arg)))
+
+(defun org-previous-visible-heading (arg)
+ "Move to the previous visible heading.
+
+This function wraps `outline-previous-visible-heading' with
+`org-with-limited-levels' in order to skip over inline tasks and
+respect customization of `org-odd-levels-only'."
+ (interactive "p")
+ (org-with-limited-levels
+ (outline-previous-visible-heading arg)))
+
(defun org-next-block (arg &optional backward block-regexp)
"Jump to the next block.
-With a prefix argument ARG, jump forward ARG many source blocks.
+
+With a prefix argument ARG, jump forward ARG many blocks.
+
When BACKWARD is non-nil, jump to the previous block.
-When BLOCK-REGEXP is non-nil, use this regexp to find blocks."
+
+When BLOCK-REGEXP is non-nil, use this regexp to find blocks.
+Match data is set according to this regexp when the function
+returns.
+
+Return point at beginning of the opening line of found block.
+Throw an error if no block is found."
(interactive "p")
- (let ((re (or block-regexp org-block-regexp))
- (re-search-fn (or (and backward 're-search-backward)
- 're-search-forward)))
- (if (looking-at re) (forward-char 1))
- (condition-case nil
- (funcall re-search-fn re nil nil arg)
- (error (error "No %s code blocks" (if backward "previous" "further" ))))
- (goto-char (match-beginning 0)) (org-show-context)))
+ (let ((re (or block-regexp "^[ \t]*#\\+BEGIN"))
+ (case-fold-search t)
+ (search-fn (if backward #'re-search-backward #'re-search-forward))
+ (count (or arg 1))
+ (origin (point))
+ last-element)
+ (if backward (beginning-of-line) (end-of-line))
+ (while (and (> count 0) (funcall search-fn re nil t))
+ (let ((element (save-excursion
+ (goto-char (match-beginning 0))
+ (save-match-data (org-element-at-point)))))
+ (when (and (memq (org-element-type element)
+ '(center-block comment-block dynamic-block
+ example-block export-block quote-block
+ special-block src-block verse-block))
+ (<= (match-beginning 0)
+ (org-element-property :post-affiliated element)))
+ (setq last-element element)
+ (cl-decf count))))
+ (if (= count 0)
+ (prog1 (goto-char (org-element-property :post-affiliated last-element))
+ (save-match-data (org-show-context)))
+ (goto-char origin)
+ (user-error "No %s code blocks" (if backward "previous" "further")))))
(defun org-previous-block (arg &optional block-regexp)
"Jump to the previous block.
@@ -23434,7 +24432,7 @@ item, etc. It also provides some special moves for convenience:
(skip-chars-forward " \r\t\n")
(or (eobp) (beginning-of-line)))
;; On affiliated keywords, move to element's beginning.
- ((and post-affiliated (< (point) post-affiliated))
+ ((< (point) post-affiliated)
(goto-char post-affiliated))
;; At a table row, move to the end of the table. Similarly,
;; at a node property, move to the end of the property
@@ -23461,8 +24459,8 @@ item, etc. It also provides some special moves for convenience:
;; With no contents, just skip element.
((not contents-begin) (goto-char end))
;; If contents are invisible, skip the element altogether.
- ((outline-invisible-p (line-end-position))
- (case type
+ ((org-invisible-p (line-end-position))
+ (cl-case type
(headline
(org-with-limited-levels (outline-next-visible-heading 1)))
;; At a plain list, make sure we move to the next item
@@ -23473,7 +24471,7 @@ item, etc. It also provides some special moves for convenience:
((>= (point) contents-end) (goto-char end))
((>= (point) contents-begin)
;; This can only happen on paragraphs and plain lists.
- (case type
+ (cl-case type
(paragraph (goto-char end))
;; At a plain list, try to move to second element in
;; first item, if possible.
@@ -23513,7 +24511,7 @@ convenience:
((= (point) begin)
(backward-char)
(org-backward-paragraph))
- ((and post-affiliated (<= (point) post-affiliated)) (goto-char begin))
+ ((<= (point) post-affiliated) (goto-char begin))
((memq type '(node-property table-row))
(goto-char (org-element-property
:post-affiliated (org-element-property :parent element))))
@@ -23548,7 +24546,7 @@ convenience:
(org-backward-paragraph))
(t (goto-char (or post-affiliated begin))))
;; Ensure we never leave point invisible.
- (when (outline-invisible-p (point)) (beginning-of-visual-line))))
+ (when (org-invisible-p (point)) (beginning-of-visual-line))))
(defun org-forward-element ()
"Move forward by one element.
@@ -23587,18 +24585,21 @@ Move to the previous element at the same level, when possible."
(progn (goto-char origin)
(user-error "Cannot move further up"))))))
(t
- (let* ((trail (org-element-at-point 'keep-trail))
- (elem (car trail))
- (prev-elem (nth 1 trail))
+ (let* ((elem (org-element-at-point))
(beg (org-element-property :begin elem)))
(cond
;; Move to beginning of current element if point isn't
;; there already.
((null beg) (message "No element at point"))
((/= (point) beg) (goto-char beg))
- (prev-elem (goto-char (org-element-property :begin prev-elem)))
- ((org-before-first-heading-p) (goto-char (point-min)))
- (t (org-back-to-heading)))))))
+ (t (goto-char beg)
+ (skip-chars-backward " \r\t\n")
+ (unless (bobp)
+ (let ((prev (org-element-at-point)))
+ (goto-char (org-element-property :begin prev))
+ (while (and (setq prev (org-element-property :parent prev))
+ (<= (org-element-property :end prev) beg))
+ (goto-char (org-element-property :begin prev)))))))))))
(defun org-up-element ()
"Move to upper element."
@@ -23612,7 +24613,6 @@ Move to the previous element at the same level, when possible."
(user-error "No surrounding element")
(org-with-limited-levels (org-back-to-heading)))))))
-(defvar org-element-greater-elements)
(defun org-down-element ()
"Move to inner element."
(interactive)
@@ -23623,7 +24623,7 @@ Move to the previous element at the same level, when possible."
(forward-char))
((memq (org-element-type element) org-element-greater-elements)
;; If contents are hidden, first disclose them.
- (when (org-element-property :hiddenp element) (org-cycle))
+ (when (org-invisible-p (line-end-position)) (org-cycle))
(goto-char (or (org-element-property :contents-begin element)
(user-error "No content for this element"))))
(t (user-error "No inner element")))))
@@ -23631,24 +24631,41 @@ Move to the previous element at the same level, when possible."
(defun org-drag-element-backward ()
"Move backward element at point."
(interactive)
- (if (org-with-limited-levels (org-at-heading-p)) (org-move-subtree-up)
- (let* ((trail (org-element-at-point 'keep-trail))
- (elem (car trail))
- (prev-elem (nth 1 trail)))
- ;; Error out if no previous element or previous element is
- ;; a parent of the current one.
- (if (or (not prev-elem) (org-element-nested-p elem prev-elem))
- (user-error "Cannot drag element backward")
- (let ((pos (point)))
- (org-element-swap-A-B prev-elem elem)
- (goto-char (+ (org-element-property :begin prev-elem)
- (- pos (org-element-property :begin elem)))))))))
+ (let ((elem (or (org-element-at-point)
+ (user-error "No element at point"))))
+ (if (eq (org-element-type elem) 'headline)
+ ;; Preserve point when moving a whole tree, even if point was
+ ;; on blank lines below the headline.
+ (let ((offset (skip-chars-backward " \t\n")))
+ (unwind-protect (org-move-subtree-up)
+ (forward-char (- offset))))
+ (let ((prev-elem
+ (save-excursion
+ (goto-char (org-element-property :begin elem))
+ (skip-chars-backward " \r\t\n")
+ (unless (bobp)
+ (let* ((beg (org-element-property :begin elem))
+ (prev (org-element-at-point))
+ (up prev))
+ (while (and (setq up (org-element-property :parent up))
+ (<= (org-element-property :end up) beg))
+ (setq prev up))
+ prev)))))
+ ;; Error out if no previous element or previous element is
+ ;; a parent of the current one.
+ (if (or (not prev-elem) (org-element-nested-p elem prev-elem))
+ (user-error "Cannot drag element backward")
+ (let ((pos (point)))
+ (org-element-swap-A-B prev-elem elem)
+ (goto-char (+ (org-element-property :begin prev-elem)
+ (- pos (org-element-property :begin elem))))))))))
(defun org-drag-element-forward ()
"Move forward element at point."
(interactive)
(let* ((pos (point))
- (elem (org-element-at-point)))
+ (elem (or (org-element-at-point)
+ (user-error "No element at point"))))
(when (= (point-max) (org-element-property :end elem))
(user-error "Cannot drag element forward"))
(goto-char (org-element-property :end elem))
@@ -23681,7 +24698,7 @@ Move to the previous element at the same level, when possible."
(defun org-drag-line-forward (arg)
"Drag the line at point ARG lines forward."
(interactive "p")
- (dotimes (n (abs arg))
+ (dotimes (_ (abs arg))
(let ((c (current-column)))
(if (< 0 arg)
(progn
@@ -23705,7 +24722,7 @@ mode) if the mark is active, it marks the next element after the
ones already marked."
(interactive)
(let (deactivate-mark)
- (if (and (org-called-interactively-p 'any)
+ (if (and (called-interactively-p 'any)
(or (and (eq last-command this-command) (mark t))
(and transient-mark-mode mark-active)))
(set-mark
@@ -23751,13 +24768,10 @@ modified."
(interactive)
(unless (eq major-mode 'org-mode)
(user-error "Cannot un-indent a buffer not in Org mode"))
- (let* ((parse-tree (org-element-parse-buffer 'greater-element))
- unindent-tree ; For byte-compiler.
- (unindent-tree
- (function
- (lambda (contents)
- (mapc
- (lambda (element)
+ (letrec ((parse-tree (org-element-parse-buffer 'greater-element))
+ (unindent-tree
+ (lambda (contents)
+ (dolist (element (reverse contents))
(if (memq (org-element-type element) '(headline section))
(funcall unindent-tree (org-element-contents element))
(save-excursion
@@ -23765,10 +24779,49 @@ modified."
(narrow-to-region
(org-element-property :begin element)
(org-element-property :end element))
- (org-do-remove-indentation)))))
- (reverse contents))))))
+ (org-do-remove-indentation))))))))
(funcall unindent-tree (org-element-contents parse-tree))))
+(defun org-show-children (&optional level)
+ "Show all direct subheadings of this heading.
+Prefix arg LEVEL is how many levels below the current level
+should be shown. Default is enough to cause the following
+heading to appear."
+ (interactive "p")
+ ;; If `orgstruct-mode' is active, use the slower version.
+ (if orgstruct-mode (call-interactively #'outline-show-children)
+ (save-excursion
+ (org-back-to-heading t)
+ (let* ((current-level (funcall outline-level))
+ (max-level (org-get-valid-level
+ current-level
+ (if level (prefix-numeric-value level) 1)))
+ (end (save-excursion (org-end-of-subtree t t)))
+ (regexp-fmt "^\\*\\{%d,%s\\}\\(?: \\|$\\)")
+ (past-first-child nil)
+ ;; Make sure to skip inlinetasks.
+ (re (format regexp-fmt
+ current-level
+ (cond
+ ((not (featurep 'org-inlinetask)) "")
+ (org-odd-levels-only (- (* 2 org-inlinetask-min-level)
+ 3))
+ (t (1- org-inlinetask-min-level))))))
+ ;; Display parent heading.
+ (outline-flag-region (line-end-position 0) (line-end-position) nil)
+ (forward-line)
+ ;; Display children. First child may be deeper than expected
+ ;; MAX-LEVEL. Since we want to display it anyway, adjust
+ ;; MAX-LEVEL accordingly.
+ (while (re-search-forward re end t)
+ (unless past-first-child
+ (setq re (format regexp-fmt
+ current-level
+ (max (funcall outline-level) max-level)))
+ (setq past-first-child t))
+ (outline-flag-region
+ (line-end-position 0) (line-end-position) nil))))))
+
(defun org-show-subtree ()
"Show everything after this heading at deeper levels."
(interactive)
@@ -23783,58 +24836,33 @@ modified."
Show the heading too, if it is currently invisible."
(interactive)
(save-excursion
- (condition-case nil
- (progn
- (org-back-to-heading t)
- (outline-flag-region
- (max (point-min) (1- (point)))
- (save-excursion
- (if (re-search-forward
- (concat "[\r\n]\\(" org-outline-regexp "\\)") nil t)
- (match-beginning 1)
- (point-max)))
- nil)
- (org-cycle-hide-drawers 'children))
- (error nil))))
+ (ignore-errors
+ (org-back-to-heading t)
+ (outline-flag-region
+ (max (point-min) (1- (point)))
+ (save-excursion
+ (if (re-search-forward
+ (concat "[\r\n]\\(" org-outline-regexp "\\)") nil t)
+ (match-beginning 1)
+ (point-max)))
+ nil)
+ (org-cycle-hide-drawers 'children))))
(defun org-make-options-regexp (kwds &optional extra)
- "Make a regular expression for keyword lines."
- (concat
- "^#\\+\\("
- (mapconcat 'regexp-quote kwds "\\|")
- (if extra (concat "\\|" extra))
- "\\):[ \t]*\\(.*\\)"))
-
-;; Make isearch reveal the necessary context
-(defun org-isearch-end ()
- "Reveal context after isearch exits."
- (when isearch-success ; only if search was successful
- (if (featurep 'xemacs)
- ;; Under XEmacs, the hook is run in the correct place,
- ;; we directly show the context.
- (org-show-context 'isearch)
- ;; In Emacs the hook runs *before* restoring the overlays.
- ;; So we have to use a one-time post-command-hook to do this.
- ;; (Emacs 22 has a special variable, see function `org-mode')
- (unless (and (boundp 'isearch-mode-end-hook-quit)
- isearch-mode-end-hook-quit)
- ;; Only when the isearch was not quitted.
- (org-add-hook 'post-command-hook 'org-isearch-post-command
- 'append 'local)))))
-
-(defun org-isearch-post-command ()
- "Remove self from hook, and show context."
- (remove-hook 'post-command-hook 'org-isearch-post-command 'local)
- (org-show-context 'isearch))
-
+ "Make a regular expression for keyword lines.
+KWDS is a list of keywords, as strings. Optional argument EXTRA,
+when non-nil, is a regexp matching keywords names."
+ (concat "^[ \t]*#\\+\\("
+ (regexp-opt kwds)
+ (and extra (concat (and kwds "\\|") extra))
+ "\\):[ \t]*\\(.*\\)"))
;;;; Integration with and fixes for other packages
;;; Imenu support
-(defvar org-imenu-markers nil
+(defvar-local org-imenu-markers nil
"All markers currently used by Imenu.")
-(make-variable-buffer-local 'org-imenu-markers)
(defun org-imenu-new-marker (&optional pos)
"Return a new marker for use by Imenu, and remember the marker."
@@ -23845,50 +24873,48 @@ Show the heading too, if it is currently invisible."
(defun org-imenu-get-tree ()
"Produce the index for Imenu."
- (mapc (lambda (x) (move-marker x nil)) org-imenu-markers)
+ (dolist (x org-imenu-markers) (move-marker x nil))
(setq org-imenu-markers nil)
- (let* ((n org-imenu-depth)
+ (let* ((case-fold-search nil)
+ (n org-imenu-depth)
(re (concat "^" (org-get-limited-outline-regexp)))
(subs (make-vector (1+ n) nil))
(last-level 0)
m level head0 head)
- (save-excursion
- (save-restriction
- (widen)
- (goto-char (point-max))
- (while (re-search-backward re nil t)
- (setq level (org-reduced-level (funcall outline-level)))
- (when (and (<= level n)
- (looking-at org-complex-heading-regexp)
- (setq head0 (org-match-string-no-properties 4)))
- (setq head (org-link-display-format head0)
- m (org-imenu-new-marker))
- (org-add-props head nil 'org-imenu-marker m 'org-imenu t)
- (if (>= level last-level)
- (push (cons head m) (aref subs level))
- (push (cons head (aref subs (1+ level))) (aref subs level))
- (loop for i from (1+ level) to n do (aset subs i nil)))
- (setq last-level level)))))
+ (org-with-wide-buffer
+ (goto-char (point-max))
+ (while (re-search-backward re nil t)
+ (setq level (org-reduced-level (funcall outline-level)))
+ (when (and (<= level n)
+ (looking-at org-complex-heading-regexp)
+ (setq head0 (match-string-no-properties 4)))
+ (setq head (org-link-display-format head0)
+ m (org-imenu-new-marker))
+ (org-add-props head nil 'org-imenu-marker m 'org-imenu t)
+ (if (>= level last-level)
+ (push (cons head m) (aref subs level))
+ (push (cons head (aref subs (1+ level))) (aref subs level))
+ (cl-loop for i from (1+ level) to n do (aset subs i nil)))
+ (setq last-level level))))
(aref subs 1)))
(eval-after-load "imenu"
'(progn
(add-hook 'imenu-after-jump-hook
(lambda ()
- (if (derived-mode-p 'org-mode)
- (org-show-context 'org-goto))))))
+ (when (derived-mode-p 'org-mode)
+ (org-show-context 'org-goto))))))
-(defun org-link-display-format (link)
- "Replace a link with its the description.
+(defun org-link-display-format (s)
+ "Replace links in string S with their description.
If there is no description, use the link target."
(save-match-data
- (if (string-match org-bracket-link-analytic-regexp link)
- (replace-match (if (match-end 5)
- (match-string 5 link)
- (concat (match-string 1 link)
- (match-string 3 link)))
- nil t link)
- link)))
+ (replace-regexp-in-string
+ org-bracket-link-analytic-regexp
+ (lambda (m)
+ (if (match-end 5) (match-string 5 m)
+ (concat (match-string 1 m) (match-string 3 m))))
+ s nil t)))
(defun org-toggle-link-display ()
"Toggle the literal or descriptive display of links."
@@ -23909,11 +24935,11 @@ If there is no description, use the link target."
'face 'org-agenda-restriction-lock)
(overlay-put org-speedbar-restriction-lock-overlay
'help-echo "Agendas are currently limited to this item.")
-(org-detach-overlay org-speedbar-restriction-lock-overlay)
+(delete-overlay org-speedbar-restriction-lock-overlay)
(defun org-speedbar-set-agenda-restriction ()
"Restrict future agenda commands to the location at point in speedbar.
-To get rid of the restriction, use \\[org-agenda-remove-restriction-lock]."
+To get rid of the restriction, use `\\[org-agenda-remove-restriction-lock]'."
(interactive)
(require 'org-agenda)
(let (p m tp np dir txt)
@@ -23937,9 +24963,9 @@ To get rid of the restriction, use \\[org-agenda-remove-restriction-lock]."
(let ((default-directory dir))
(expand-file-name txt)))
(unless (derived-mode-p 'org-mode)
- (user-error "Cannot restrict to non-Org-mode file"))
+ (user-error "Cannot restrict to non-Org mode file"))
(org-agenda-set-restriction-lock 'file)))
- (t (user-error "Don't know how to restrict Org-mode's agenda")))
+ (t (user-error "Don't know how to restrict Org mode agenda")))
(move-overlay org-speedbar-restriction-lock-overlay
(point-at-bol) (point-at-eol))
(setq current-prefix-arg nil)
@@ -23959,34 +24985,98 @@ To get rid of the restriction, use \\[org-agenda-remove-restriction-lock]."
;;; Fixes and Hacks for problems with other packages
-;; Make flyspell not check words in links, to not mess up our keymap
-(defvar org-element-affiliated-keywords) ; From org-element.el
-(defvar org-element-block-name-alist) ; From org-element.el
+(defun org--flyspell-object-check-p (element)
+ "Non-nil when Flyspell can check object at point.
+ELEMENT is the element at point."
+ (let ((object (save-excursion
+ (when (looking-at-p "\\>") (backward-char))
+ (org-element-context element))))
+ (cl-case (org-element-type object)
+ ;; Prevent checks in links due to keybinding conflict with
+ ;; Flyspell.
+ ((code entity export-snippet inline-babel-call
+ inline-src-block line-break latex-fragment link macro
+ statistics-cookie target timestamp verbatim)
+ nil)
+ (footnote-reference
+ ;; Only in inline footnotes, within the definition.
+ (and (eq (org-element-property :type object) 'inline)
+ (< (save-excursion
+ (goto-char (org-element-property :begin object))
+ (search-forward ":" nil t 2))
+ (point))))
+ (otherwise t))))
+
(defun org-mode-flyspell-verify ()
- "Don't let flyspell put overlays at active buttons, or on
- {todo,all-time,additional-option-like}-keywords."
- (require 'org-element) ; For `org-element-affiliated-keywords'
- (let ((pos (max (1- (point)) (point-min)))
- (word (thing-at-point 'word)))
- (and (not (get-text-property pos 'keymap))
- (not (get-text-property pos 'org-no-flyspell))
- (not (member word org-todo-keywords-1))
- (not (member word org-all-time-keywords))
- (not (member word org-options-keywords))
- (not (member word (mapcar 'car org-startup-options)))
- (not (member-ignore-case word org-element-affiliated-keywords))
- (not (member-ignore-case word (org-get-export-keywords)))
- (not (member-ignore-case
- word (mapcar 'car org-element-block-name-alist)))
- (not (member-ignore-case word '("BEGIN" "END" "ATTR")))
- (not (org-in-src-block-p)))))
+ "Function used for `flyspell-generic-check-word-predicate'."
+ (if (org-at-heading-p)
+ ;; At a headline or an inlinetask, check title only. This is
+ ;; faster than relying on `org-element-at-point'.
+ (and (save-excursion (beginning-of-line)
+ (and (let ((case-fold-search t))
+ (not (looking-at-p "\\*+ END[ \t]*$")))
+ (let ((case-fold-search nil))
+ (looking-at org-complex-heading-regexp))))
+ (match-beginning 4)
+ (>= (point) (match-beginning 4))
+ (or (not (match-beginning 5))
+ (< (point) (match-beginning 5))))
+ (let* ((element (org-element-at-point))
+ (post-affiliated (org-element-property :post-affiliated element)))
+ (cond
+ ;; Ignore checks in all affiliated keywords but captions.
+ ((< (point) post-affiliated)
+ (and (save-excursion
+ (beginning-of-line)
+ (let ((case-fold-search t)) (looking-at "[ \t]*#\\+CAPTION:")))
+ (> (point) (match-end 0))
+ (org--flyspell-object-check-p element)))
+ ;; Ignore checks in LOGBOOK (or equivalent) drawer.
+ ((let ((log (org-log-into-drawer)))
+ (and log
+ (let ((drawer (org-element-lineage element '(drawer))))
+ (and drawer
+ (eq (compare-strings
+ log nil nil
+ (org-element-property :drawer-name drawer) nil nil t)
+ t)))))
+ nil)
+ (t
+ (cl-case (org-element-type element)
+ ((comment quote-section) t)
+ (comment-block
+ ;; Allow checks between block markers, not on them.
+ (and (> (line-beginning-position) post-affiliated)
+ (save-excursion
+ (end-of-line)
+ (skip-chars-forward " \r\t\n")
+ (< (point) (org-element-property :end element)))))
+ ;; Arbitrary list of keywords where checks are meaningful.
+ ;; Make sure point is on the value part of the element.
+ (keyword
+ (and (member (org-element-property :key element)
+ '("DESCRIPTION" "TITLE"))
+ (save-excursion
+ (search-backward ":" (line-beginning-position) t))))
+ ;; Check is globally allowed in paragraphs verse blocks and
+ ;; table rows (after affiliated keywords) but some objects
+ ;; must not be affected.
+ ((paragraph table-row verse-block)
+ (let ((cbeg (org-element-property :contents-begin element))
+ (cend (org-element-property :contents-end element)))
+ (and cbeg (>= (point) cbeg) (< (point) cend)
+ (org--flyspell-object-check-p element))))))))))
+(put 'org-mode 'flyspell-mode-predicate 'org-mode-flyspell-verify)
(defun org-remove-flyspell-overlays-in (beg end)
"Remove flyspell overlays in region."
- (and (org-bound-and-true-p flyspell-mode)
+ (and (bound-and-true-p flyspell-mode)
(fboundp 'flyspell-delete-region-overlays)
- (flyspell-delete-region-overlays beg end))
- (add-text-properties beg end '(org-no-flyspell t)))
+ (flyspell-delete-region-overlays beg end)))
+
+(defvar flyspell-delayed-commands)
+(eval-after-load "flyspell"
+ '(add-to-list 'flyspell-delayed-commands 'org-self-insert-command))
;; Make `bookmark-jump' shows the jump location if it was hidden.
(eval-after-load "bookmark"
@@ -24008,17 +25098,38 @@ To get rid of the restriction, use \\[org-agenda-remove-restriction-lock]."
(eval-after-load "ecb"
'(defadvice ecb-method-clicked (after esf/org-show-context activate)
"Make hierarchy visible when jumping into location from ECB tree buffer."
- (if (derived-mode-p 'org-mode)
- (org-show-context))))
+ (when (derived-mode-p 'org-mode)
+ (org-show-context))))
(defun org-bookmark-jump-unhide ()
"Unhide the current position, to show the bookmark location."
(and (derived-mode-p 'org-mode)
- (or (outline-invisible-p)
+ (or (org-invisible-p)
(save-excursion (goto-char (max (point-min) (1- (point))))
- (outline-invisible-p)))
+ (org-invisible-p)))
(org-show-context 'bookmark-jump)))
+(defun org-mark-jump-unhide ()
+ "Make the point visible with `org-show-context' after jumping to the mark."
+ (when (and (derived-mode-p 'org-mode)
+ (org-invisible-p))
+ (org-show-context 'mark-goto)))
+
+(eval-after-load "simple"
+ '(defadvice pop-to-mark-command (after org-make-visible activate)
+ "Make the point visible with `org-show-context'."
+ (org-mark-jump-unhide)))
+
+(eval-after-load "simple"
+ '(defadvice exchange-point-and-mark (after org-make-visible activate)
+ "Make the point visible with `org-show-context'."
+ (org-mark-jump-unhide)))
+
+(eval-after-load "simple"
+ '(defadvice pop-global-mark (after org-make-visible activate)
+ "Make the point visible with `org-show-context'."
+ (org-mark-jump-unhide)))
+
;; Make session.el ignore our circular variable
(defvar session-globals-exclude)
(eval-after-load "session"
diff --git a/lisp/org/ox-ascii.el b/lisp/org/ox-ascii.el
index 6ba70d700b2..8dc31be99d7 100644
--- a/lisp/org/ox-ascii.el
+++ b/lisp/org/ox-ascii.el
@@ -1,4 +1,4 @@
-;;; ox-ascii.el --- ASCII Back-End for Org Export Engine
+;;; ox-ascii.el --- ASCII Back-End for Org Export Engine -*- lexical-binding: t; -*-
;; Copyright (C) 2012-2017 Free Software Foundation, Inc.
@@ -27,9 +27,9 @@
;;; Code:
-(eval-when-compile (require 'cl))
(require 'ox)
(require 'ox-publish)
+(require 'cl-lib)
(declare-function aa2u "ext:ascii-art-to-unicode" ())
@@ -49,8 +49,6 @@
(center-block . org-ascii-center-block)
(clock . org-ascii-clock)
(code . org-ascii-code)
- (comment . (lambda (&rest args) ""))
- (comment-block . (lambda (&rest args) ""))
(drawer . org-ascii-drawer)
(dynamic-block . org-ascii-dynamic-block)
(entity . org-ascii-entity)
@@ -71,12 +69,13 @@
(latex-fragment . org-ascii-latex-fragment)
(line-break . org-ascii-line-break)
(link . org-ascii-link)
+ (node-property . org-ascii-node-property)
(paragraph . org-ascii-paragraph)
(plain-list . org-ascii-plain-list)
(plain-text . org-ascii-plain-text)
(planning . org-ascii-planning)
+ (property-drawer . org-ascii-property-drawer)
(quote-block . org-ascii-quote-block)
- (quote-section . org-ascii-quote-section)
(radio-target . org-ascii-radio-target)
(section . org-ascii-section)
(special-block . org-ascii-special-block)
@@ -94,7 +93,6 @@
(underline . org-ascii-underline)
(verbatim . org-ascii-verbatim)
(verse-block . org-ascii-verse-block))
- :export-block "ASCII"
:menu-entry
'(?t "Export to Plain Text"
((?A "As ASCII buffer"
@@ -119,7 +117,30 @@
(:filter-parse-tree org-ascii-filter-paragraph-spacing
org-ascii-filter-comment-spacing)
(:filter-section . org-ascii-filter-headline-blank-lines))
- :options-alist '((:ascii-charset nil nil org-ascii-charset)))
+ :options-alist
+ '((:subtitle "SUBTITLE" nil nil parse)
+ (:ascii-bullets nil nil org-ascii-bullets)
+ (:ascii-caption-above nil nil org-ascii-caption-above)
+ (:ascii-charset nil nil org-ascii-charset)
+ (:ascii-global-margin nil nil org-ascii-global-margin)
+ (:ascii-format-drawer-function nil nil org-ascii-format-drawer-function)
+ (:ascii-format-inlinetask-function
+ nil nil org-ascii-format-inlinetask-function)
+ (:ascii-headline-spacing nil nil org-ascii-headline-spacing)
+ (:ascii-indented-line-width nil nil org-ascii-indented-line-width)
+ (:ascii-inlinetask-width nil nil org-ascii-inlinetask-width)
+ (:ascii-inner-margin nil nil org-ascii-inner-margin)
+ (:ascii-links-to-notes nil nil org-ascii-links-to-notes)
+ (:ascii-list-margin nil nil org-ascii-list-margin)
+ (:ascii-paragraph-spacing nil nil org-ascii-paragraph-spacing)
+ (:ascii-quote-margin nil nil org-ascii-quote-margin)
+ (:ascii-table-keep-all-vertical-lines
+ nil nil org-ascii-table-keep-all-vertical-lines)
+ (:ascii-table-use-ascii-art nil nil org-ascii-table-use-ascii-art)
+ (:ascii-table-widen-columns nil nil org-ascii-table-widen-columns)
+ (:ascii-text-width nil nil org-ascii-text-width)
+ (:ascii-underline nil nil org-ascii-underline)
+ (:ascii-verbatim-format nil nil org-ascii-verbatim-format)))
@@ -162,6 +183,15 @@ This margin is applied on both sides of the text."
:package-version '(Org . "8.0")
:type 'integer)
+(defcustom org-ascii-list-margin 0
+ "Width of margin used for plain lists, in characters.
+This margin applies to top level list only, not to its
+sub-lists."
+ :group 'org-export-ascii
+ :version "26.1"
+ :package-version '(Org . "8.3")
+ :type 'integer)
+
(defcustom org-ascii-inlinetask-width 30
"Width of inline tasks, in number of characters.
This number ignores any margin."
@@ -339,7 +369,7 @@ Otherwise, place it right after it."
:type 'string)
(defcustom org-ascii-format-drawer-function
- (lambda (name contents width) contents)
+ (lambda (_name contents _width) contents)
"Function called to format a drawer in ASCII.
The function must accept three parameters:
@@ -384,14 +414,18 @@ nil to ignore the inline task."
;; Internal functions fall into three categories.
-;; The first one is about text formatting. The core function is
-;; `org-ascii--current-text-width', which determines the current
-;; text width allowed to a given element. In other words, it helps
-;; keeping each line width within maximum text width defined in
-;; `org-ascii-text-width'. Once this information is known,
-;; `org-ascii--fill-string', `org-ascii--justify-string',
-;; `org-ascii--box-string' and `org-ascii--indent-string' can
-;; operate on a given output string.
+;; The first one is about text formatting. The core functions are
+;; `org-ascii--current-text-width' and
+;; `org-ascii--current-justification', which determine, respectively,
+;; the current text width allowed to a given element and its expected
+;; justification. Once this information is known,
+;; `org-ascii--fill-string', `org-ascii--justify-lines',
+;; `org-ascii--justify-element' `org-ascii--box-string' and
+;; `org-ascii--indent-string' can operate on a given output string.
+;; In particular, justification happens at the regular (i.e.,
+;; non-greater) element level, which means that when the exporting
+;; process reaches a container (e.g., a center block) content are
+;; already justified.
;; The second category contains functions handling elements listings,
;; triggered by "#+TOC:" keyword. As such, `org-ascii--build-toc'
@@ -420,7 +454,8 @@ a communication channel.
Optional argument JUSTIFY can specify any type of justification
among `left', `center', `right' or `full'. A nil value is
equivalent to `left'. For a justification that doesn't also fill
-string, see `org-ascii--justify-string'.
+string, see `org-ascii--justify-lines' and
+`org-ascii--justify-block'.
Return nil if S isn't a string."
(when (stringp s)
@@ -435,8 +470,8 @@ Return nil if S isn't a string."
(fill-region (point-min) (point-max) justify))
(buffer-string)))))
-(defun org-ascii--justify-string (s text-width how)
- "Justify string S.
+(defun org-ascii--justify-lines (s text-width how)
+ "Justify all lines in string S.
TEXT-WIDTH is an integer specifying maximum length of a line.
HOW determines the type of justification: it can be `left',
`right', `full' or `center'."
@@ -452,6 +487,48 @@ HOW determines the type of justification: it can be `left',
(forward-line)))
(buffer-string)))
+(defun org-ascii--justify-element (contents element info)
+ "Justify CONTENTS of ELEMENT.
+INFO is a plist used as a communication channel. Justification
+is done according to the type of element. More accurately,
+paragraphs are filled and other elements are justified as blocks,
+that is according to the widest non blank line in CONTENTS."
+ (if (not (org-string-nw-p contents)) contents
+ (let ((text-width (org-ascii--current-text-width element info))
+ (how (org-ascii--current-justification element)))
+ (cond
+ ((eq (org-element-type element) 'paragraph)
+ ;; Paragraphs are treated specially as they need to be filled.
+ (org-ascii--fill-string contents text-width info how))
+ ((eq how 'left) contents)
+ (t (with-temp-buffer
+ (insert contents)
+ (goto-char (point-min))
+ (catch 'exit
+ (let ((max-width 0))
+ ;; Compute maximum width. Bail out if it is greater
+ ;; than page width, since no justification is
+ ;; possible.
+ (save-excursion
+ (while (not (eobp))
+ (unless (looking-at-p "[ \t]*$")
+ (end-of-line)
+ (let ((column (current-column)))
+ (cond
+ ((>= column text-width) (throw 'exit contents))
+ ((> column max-width) (setq max-width column)))))
+ (forward-line)))
+ ;; Justify every line according to TEXT-WIDTH and
+ ;; MAX-WIDTH.
+ (let ((offset (/ (- text-width max-width)
+ (if (eq how 'right) 1 2))))
+ (if (zerop offset) (throw 'exit contents)
+ (while (not (eobp))
+ (unless (looking-at-p "[ \t]*$")
+ (indent-to-column offset))
+ (forward-line)))))
+ (buffer-string))))))))
+
(defun org-ascii--indent-string (s width)
"Indent string S by WIDTH white spaces.
Empty lines are not indented."
@@ -472,26 +549,28 @@ INFO is a plist used as a communication channel."
(defun org-ascii--current-text-width (element info)
"Return maximum text width for ELEMENT's contents.
INFO is a plist used as a communication channel."
- (case (org-element-type element)
+ (pcase (org-element-type element)
;; Elements with an absolute width: `headline' and `inlinetask'.
- (inlinetask org-ascii-inlinetask-width)
- (headline
- (- org-ascii-text-width
+ (`inlinetask (plist-get info :ascii-inlinetask-width))
+ (`headline
+ (- (plist-get info :ascii-text-width)
(let ((low-level-rank (org-export-low-level-p element info)))
- (if low-level-rank (* low-level-rank 2) org-ascii-global-margin))))
+ (if low-level-rank (* low-level-rank 2)
+ (plist-get info :ascii-global-margin)))))
;; Elements with a relative width: store maximum text width in
;; TOTAL-WIDTH.
- (otherwise
- (let* ((genealogy (cons element (org-export-get-genealogy element)))
+ (_
+ (let* ((genealogy (org-element-lineage element nil t))
;; Total width is determined by the presence, or not, of an
;; inline task among ELEMENT parents.
(total-width
- (if (loop for parent in genealogy
- thereis (eq (org-element-type parent) 'inlinetask))
- org-ascii-inlinetask-width
+ (if (cl-some (lambda (parent)
+ (eq (org-element-type parent) 'inlinetask))
+ genealogy)
+ (plist-get info :ascii-inlinetask-width)
;; No inlinetask: Remove global margin from text width.
- (- org-ascii-text-width
- org-ascii-global-margin
+ (- (plist-get info :ascii-text-width)
+ (plist-get info :ascii-global-margin)
(let ((parent (org-export-get-parent-headline element)))
;; Inner margin doesn't apply to text before first
;; headline.
@@ -502,41 +581,67 @@ INFO is a plist used as a communication channel."
;; low level headlines, since they've got their
;; own indentation mechanism.
(if low-level-rank (* low-level-rank 2)
- org-ascii-inner-margin))))))))
+ (plist-get info :ascii-inner-margin)))))))))
(- total-width
- ;; Each `quote-block', `quote-section' and `verse-block' above
- ;; narrows text width by twice the standard margin size.
- (+ (* (loop for parent in genealogy
- when (memq (org-element-type parent)
- '(quote-block quote-section verse-block))
- count parent)
- 2 org-ascii-quote-margin)
+ ;; Each `quote-block' and `verse-block' above narrows text
+ ;; width by twice the standard margin size.
+ (+ (* (cl-count-if (lambda (parent)
+ (memq (org-element-type parent)
+ '(quote-block verse-block)))
+ genealogy)
+ 2
+ (plist-get info :ascii-quote-margin))
+ ;; Apply list margin once per "top-level" plain-list
+ ;; containing current line
+ (* (cl-count-if
+ (lambda (e)
+ (and (eq (org-element-type e) 'plain-list)
+ (not (eq (org-element-type (org-export-get-parent e))
+ 'item))))
+ genealogy)
+ (plist-get info :ascii-list-margin))
;; Text width within a plain-list is restricted by
;; indentation of current item. If that's the case,
;; compute it with the help of `:structure' property from
;; parent item, if any.
- (let ((parent-item
+ (let ((item
(if (eq (org-element-type element) 'item) element
- (loop for parent in genealogy
- when (eq (org-element-type parent) 'item)
- return parent))))
- (if (not parent-item) 0
+ (cl-find-if (lambda (parent)
+ (eq (org-element-type parent) 'item))
+ genealogy))))
+ (if (not item) 0
;; Compute indentation offset of the current item,
;; that is the sum of the difference between its
;; indentation and the indentation of the top item in
;; the list and current item bullet's length. Also
;; remove checkbox length, and tag length (for
;; description lists) or bullet length.
- (let ((struct (org-element-property :structure parent-item))
- (beg-item (org-element-property :begin parent-item)))
+ (let ((struct (org-element-property :structure item))
+ (beg-item (org-element-property :begin item)))
(+ (- (org-list-get-ind beg-item struct)
(org-list-get-ind
(org-list-get-top-point struct) struct))
- (string-width (or (org-ascii--checkbox parent-item info)
+ (string-width (or (org-ascii--checkbox item info)
""))
(string-width
- (or (org-list-get-tag beg-item struct)
- (org-list-get-bullet beg-item struct)))))))))))))
+ (let ((tag (org-element-property :tag item)))
+ (if tag (org-export-data tag info)
+ (org-element-property :bullet item))))))))))))))
+
+(defun org-ascii--current-justification (element)
+ "Return expected justification for ELEMENT's contents.
+Return value is a symbol among `left', `center', `right' and
+`full'."
+ (let (justification)
+ (while (and (not justification)
+ (setq element (org-element-property :parent element)))
+ (pcase (org-element-type element)
+ (`center-block (setq justification 'center))
+ (`special-block
+ (let ((name (org-element-property :type element)))
+ (cond ((string= name "JUSTIFYRIGHT") (setq justification 'right))
+ ((string= name "JUSTIFYLEFT") (setq justification 'left)))))))
+ (or justification 'left)))
(defun org-ascii--build-title
(element info text-width &optional underline notags toc)
@@ -601,14 +706,14 @@ possible. It doesn't apply to `inlinetask' elements."
(let ((under-char
(nth (1- (org-export-get-relative-level element info))
(cdr (assq (plist-get info :ascii-charset)
- org-ascii-underline)))))
+ (plist-get info :ascii-underline))))))
(and under-char
(concat "\n"
(make-string (/ (string-width first-part)
(char-width under-char))
under-char))))))))
-(defun org-ascii--has-caption-p (element info)
+(defun org-ascii--has-caption-p (element _info)
"Non-nil when ELEMENT has a caption affiliated keyword.
INFO is a plist used as a communication channel. This function
is meant to be used as a predicate for `org-export-get-ordinal'."
@@ -630,9 +735,9 @@ caption keyword."
(org-export-get-ordinal
element info nil 'org-ascii--has-caption-p))
(title-fmt (org-ascii--translate
- (case (org-element-type element)
- (table "Table %d:")
- (src-block "Listing %d:"))
+ (pcase (org-element-type element)
+ (`table "Table %d:")
+ (`src-block "Listing %d:"))
info)))
(org-ascii--fill-string
(concat (format title-fmt reference)
@@ -640,7 +745,7 @@ caption keyword."
(org-export-data caption info))
(org-ascii--current-text-width element info) info)))))
-(defun org-ascii--build-toc (info &optional n keyword)
+(defun org-ascii--build-toc (info &optional n keyword local)
"Return a table of contents.
INFO is a plist used as a communication channel.
@@ -649,28 +754,34 @@ Optional argument N, when non-nil, is an integer specifying the
depth of the table.
Optional argument KEYWORD specifies the TOC keyword, if any, from
-which the table of contents generation has been initiated."
- (let ((title (org-ascii--translate "Table of Contents" info)))
- (concat
- title "\n"
- (make-string (string-width title)
- (if (eq (plist-get info :ascii-charset) 'utf-8) ?─ ?_))
- "\n\n"
- (let ((text-width
- (if keyword (org-ascii--current-text-width keyword info)
- (- org-ascii-text-width org-ascii-global-margin))))
- (mapconcat
- (lambda (headline)
- (let* ((level (org-export-get-relative-level headline info))
- (indent (* (1- level) 3)))
- (concat
- (unless (zerop indent) (concat (make-string (1- indent) ?.) " "))
- (org-ascii--build-title
- headline info (- text-width indent) nil
- (or (not (plist-get info :with-tags))
- (eq (plist-get info :with-tags) 'not-in-toc))
- 'toc))))
- (org-export-collect-headlines info n) "\n")))))
+which the table of contents generation has been initiated.
+
+When optional argument LOCAL is non-nil, build a table of
+contents according to the current headline."
+ (concat
+ (unless local
+ (let ((title (org-ascii--translate "Table of Contents" info)))
+ (concat title "\n"
+ (make-string
+ (string-width title)
+ (if (eq (plist-get info :ascii-charset) 'utf-8) ?─ ?_))
+ "\n\n")))
+ (let ((text-width
+ (if keyword (org-ascii--current-text-width keyword info)
+ (- (plist-get info :ascii-text-width)
+ (plist-get info :ascii-global-margin)))))
+ (mapconcat
+ (lambda (headline)
+ (let* ((level (org-export-get-relative-level headline info))
+ (indent (* (1- level) 3)))
+ (concat
+ (unless (zerop indent) (concat (make-string (1- indent) ?.) " "))
+ (org-ascii--build-title
+ headline info (- text-width indent) nil
+ (or (not (plist-get info :with-tags))
+ (eq (plist-get info :with-tags) 'not-in-toc))
+ 'toc))))
+ (org-export-collect-headlines info n (and local keyword)) "\n"))))
(defun org-ascii--list-listings (keyword info)
"Return a list of listings.
@@ -685,7 +796,8 @@ generation. INFO is a plist used as a communication channel."
"\n\n"
(let ((text-width
(if keyword (org-ascii--current-text-width keyword info)
- (- org-ascii-text-width org-ascii-global-margin)))
+ (- (plist-get info :ascii-text-width)
+ (plist-get info :ascii-global-margin))))
;; Use a counter instead of retrieving ordinal of each
;; src-block.
(count 0))
@@ -696,7 +808,7 @@ generation. INFO is a plist used as a communication channel."
;; filling (like contents of a description list item).
(let* ((initial-text
(format (org-ascii--translate "Listing %d:" info)
- (incf count)))
+ (cl-incf count)))
(initial-width (string-width initial-text)))
(concat
initial-text " "
@@ -724,7 +836,8 @@ generation. INFO is a plist used as a communication channel."
"\n\n"
(let ((text-width
(if keyword (org-ascii--current-text-width keyword info)
- (- org-ascii-text-width org-ascii-global-margin)))
+ (- (plist-get info :ascii-text-width)
+ (plist-get info :ascii-global-margin))))
;; Use a counter instead of retrieving ordinal of each
;; src-block.
(count 0))
@@ -735,7 +848,7 @@ generation. INFO is a plist used as a communication channel."
;; filling (like contents of a description list item).
(let* ((initial-text
(format (org-ascii--translate "Table %d:" info)
- (incf count)))
+ (cl-incf count)))
(initial-width (string-width initial-text)))
(concat
initial-text " "
@@ -756,69 +869,106 @@ ELEMENT is either a headline element or a section element. INFO
is a plist used as a communication channel."
(let* (seen
(unique-link-p
- (function
- ;; Return LINK if it wasn't referenced so far, or nil.
- ;; Update SEEN links along the way.
- (lambda (link)
- (let ((footprint
- ;; Normalize description in footprints.
- (cons (org-element-property :raw-link link)
- (let ((contents (org-element-contents link)))
- (and contents
- (replace-regexp-in-string
- "[ \r\t\n]+" " "
- (org-trim
- (org-element-interpret-data contents))))))))
- ;; Ignore LINK if it hasn't been translated already.
- ;; It can happen if it is located in an affiliated
- ;; keyword that was ignored.
- (when (and (org-string-nw-p
- (gethash link (plist-get info :exported-data)))
- (not (member footprint seen)))
- (push footprint seen) link)))))
- ;; If at a section, find parent headline, if any, in order to
- ;; count links that might be in the title.
- (headline
- (if (eq (org-element-type element) 'headline) element
- (or (org-export-get-parent-headline element) element))))
- ;; Get all links in HEADLINE.
- (org-element-map headline 'link
- (lambda (l) (funcall unique-link-p l)) info nil nil t)))
+ ;; Return LINK if it wasn't referenced so far, or nil.
+ ;; Update SEEN links along the way.
+ (lambda (link)
+ (let ((footprint
+ ;; Normalize description in footprints.
+ (cons (org-element-property :raw-link link)
+ (let ((contents (org-element-contents link)))
+ (and contents
+ (replace-regexp-in-string
+ "[ \r\t\n]+" " "
+ (org-trim
+ (org-element-interpret-data contents))))))))
+ ;; Ignore LINK if it hasn't been translated already. It
+ ;; can happen if it is located in an affiliated keyword
+ ;; that was ignored.
+ (when (and (org-string-nw-p
+ (gethash link (plist-get info :exported-data)))
+ (not (member footprint seen)))
+ (push footprint seen) link)))))
+ (org-element-map (if (eq (org-element-type element) 'section)
+ element
+ ;; In a headline, only retrieve links in title
+ ;; and relative section, not in children.
+ (list (org-element-property :title element)
+ (car (org-element-contents element))))
+ 'link unique-link-p info nil 'headline t)))
+
+(defun org-ascii--describe-datum (datum info)
+ "Describe DATUM object or element.
+If DATUM is a string, consider it to be a file name, per
+`org-export-resolve-id-link'. INFO is the communication channel,
+as a plist."
+ (pcase (org-element-type datum)
+ (`plain-text (format "See file %s" datum)) ;External file
+ (`headline
+ (format (org-ascii--translate "See section %s" info)
+ (if (org-export-numbered-headline-p datum info)
+ (mapconcat #'number-to-string
+ (org-export-get-headline-number datum info)
+ ".")
+ (org-export-data (org-element-property :title datum) info))))
+ (_
+ (let ((number (org-export-get-ordinal
+ datum info nil #'org-ascii--has-caption-p))
+ ;; If destination is a target, make sure we can name the
+ ;; container it refers to.
+ (enumerable
+ (org-element-lineage datum
+ '(headline paragraph src-block table) t)))
+ (pcase (org-element-type enumerable)
+ (`headline
+ (format (org-ascii--translate "See section %s" info)
+ (if (org-export-numbered-headline-p enumerable info)
+ (mapconcat #'number-to-string number ".")
+ (org-export-data
+ (org-element-property :title enumerable) info))))
+ ((guard (not number))
+ (org-ascii--translate "Unknown reference" info))
+ (`paragraph
+ (format (org-ascii--translate "See figure %s" info) number))
+ (`src-block
+ (format (org-ascii--translate "See listing %s" info) number))
+ (`table
+ (format (org-ascii--translate "See table %s" info) number))
+ (_ (org-ascii--translate "Unknown reference" info)))))))
(defun org-ascii--describe-links (links width info)
"Return a string describing a list of links.
-
LINKS is a list of link type objects, as returned by
`org-ascii--unique-links'. WIDTH is the text width allowed for
the output string. INFO is a plist used as a communication
channel."
(mapconcat
(lambda (link)
- (let ((type (org-element-property :type link))
- (anchor (let ((desc (org-element-contents link)))
- (if desc (org-export-data desc info)
- (org-element-property :raw-link link)))))
+ (let* ((type (org-element-property :type link))
+ (description (org-element-contents link))
+ (anchor (org-export-data
+ (or description (org-element-property :raw-link link))
+ info)))
(cond
- ;; Coderefs, radio links and fuzzy links are ignored.
- ((member type '("coderef" "radio" "fuzzy")) nil)
- ;; Id and custom-id links: Headlines refer to their numbering.
- ((member type '("custom-id" "id"))
- (let ((dest (org-export-resolve-id-link link info)))
- (concat
- (org-ascii--fill-string
- (format
- "[%s] %s"
- anchor
- (if (not dest) (org-ascii--translate "Unknown reference" info)
- (format
- (org-ascii--translate "See section %s" info)
- (mapconcat 'number-to-string
- (org-export-get-headline-number dest info) "."))))
- width info) "\n\n")))
+ ((member type '("coderef" "radio")) nil)
+ ((member type '("custom-id" "fuzzy" "id"))
+ ;; Only links with a description need an entry. Other are
+ ;; already handled in `org-ascii-link'.
+ (when description
+ (let ((dest (if (equal type "fuzzy")
+ (org-export-resolve-fuzzy-link link info)
+ (org-export-resolve-id-link link info))))
+ (concat
+ (org-ascii--fill-string
+ (format "[%s] %s" anchor (org-ascii--describe-datum dest info))
+ width info)
+ "\n\n"))))
;; Do not add a link that cannot be resolved and doesn't have
;; any description: destination is already visible in the
;; paragraph.
((not (org-element-contents link)) nil)
+ ;; Do not add a link already handled by custom export
+ ;; functions.
+ ((org-export-custom-protocol-maybe link anchor 'ascii) nil)
(t
(concat
(org-ascii--fill-string
@@ -831,10 +981,10 @@ channel."
"Return checkbox string for ITEM or nil.
INFO is a plist used as a communication channel."
(let ((utf8p (eq (plist-get info :ascii-charset) 'utf-8)))
- (case (org-element-property :checkbox item)
- (on (if utf8p "☑ " "[X] "))
- (off (if utf8p "☐ " "[ ] "))
- (trans (if utf8p "☒ " "[-] ")))))
+ (pcase (org-element-property :checkbox item)
+ (`on (if utf8p "☑ " "[X] "))
+ (`off (if utf8p "☐ " "[ ] "))
+ (`trans (if utf8p "☒ " "[-] ")))))
@@ -843,11 +993,15 @@ INFO is a plist used as a communication channel."
(defun org-ascii-template--document-title (info)
"Return document title, as a string.
INFO is a plist used as a communication channel."
- (let* ((text-width org-ascii-text-width)
+ (let* ((text-width (plist-get info :ascii-text-width))
;; Links in the title will not be resolved later, so we make
;; sure their path is located right after them.
- (org-ascii-links-to-notes nil)
- (title (org-export-data (plist-get info :title) info))
+ (info (org-combine-plists info '(:ascii-links-to-notes nil)))
+ (with-title (plist-get info :with-title))
+ (title (org-export-data
+ (when with-title (plist-get info :title)) info))
+ (subtitle (org-export-data
+ (when with-title (plist-get info :subtitle)) info))
(author (and (plist-get info :with-author)
(let ((auth (plist-get info :author)))
(and auth (org-export-data auth info)))))
@@ -878,7 +1032,7 @@ INFO is a plist used as a communication channel."
date "\n\n\n"))
((org-string-nw-p date)
(concat
- (org-ascii--justify-string date text-width 'right)
+ (org-ascii--justify-lines date text-width 'right)
"\n\n\n"))
((and (org-string-nw-p author) (org-string-nw-p email))
(concat author "\n" email "\n\n\n"))
@@ -890,8 +1044,14 @@ INFO is a plist used as a communication channel."
(let* ((utf8p (eq (plist-get info :ascii-charset) 'utf-8))
;; Format TITLE. It may be filled if it is too wide,
;; that is wider than the two thirds of the total width.
- (title-len (min (length title) (/ (* 2 text-width) 3)))
+ (title-len (min (apply #'max
+ (mapcar #'length
+ (org-split-string
+ (concat title "\n" subtitle) "\n")))
+ (/ (* 2 text-width) 3)))
(formatted-title (org-ascii--fill-string title title-len info))
+ (formatted-subtitle (when (org-string-nw-p subtitle)
+ (org-ascii--fill-string subtitle title-len info)))
(line
(make-string
(min (+ (max title-len
@@ -899,17 +1059,16 @@ INFO is a plist used as a communication channel."
(string-width (or email "")))
2)
text-width) (if utf8p ?━ ?_))))
- (org-ascii--justify-string
+ (org-ascii--justify-lines
(concat line "\n"
(unless utf8p "\n")
(upcase formatted-title)
+ (and formatted-subtitle (concat "\n" formatted-subtitle))
(cond
((and (org-string-nw-p author) (org-string-nw-p email))
- (concat (if utf8p "\n\n\n" "\n\n") author "\n" email))
- ((org-string-nw-p author)
- (concat (if utf8p "\n\n\n" "\n\n") author))
- ((org-string-nw-p email)
- (concat (if utf8p "\n\n\n" "\n\n") email)))
+ (concat "\n\n" author "\n" email))
+ ((org-string-nw-p author) (concat "\n\n" author))
+ ((org-string-nw-p email) (concat "\n\n" email)))
"\n" line
(when (org-string-nw-p date) (concat "\n\n\n" date))
"\n\n\n") text-width 'center)))))
@@ -919,81 +1078,83 @@ INFO is a plist used as a communication channel."
CONTENTS is the transcoded contents string. INFO is a plist
holding export options."
(org-element-normalize-string
- (org-ascii--indent-string
- (concat
- ;; 1. Document's body.
- contents
- ;; 2. Footnote definitions.
- (let ((definitions (org-export-collect-footnote-definitions
- (plist-get info :parse-tree) info))
- ;; Insert full links right inside the footnote definition
- ;; as they have no chance to be inserted later.
- (org-ascii-links-to-notes nil))
- (when definitions
- (concat
- "\n\n\n"
- (let ((title (org-ascii--translate "Footnotes" info)))
- (concat
- title "\n"
- (make-string
- (string-width title)
- (if (eq (plist-get info :ascii-charset) 'utf-8) ?─ ?_))))
- "\n\n"
- (let ((text-width (- org-ascii-text-width org-ascii-global-margin)))
- (mapconcat
- (lambda (ref)
- (let ((id (format "[%s] " (car ref))))
- ;; Distinguish between inline definitions and
- ;; full-fledged definitions.
- (org-trim
- (let ((def (nth 2 ref)))
- (if (eq (org-element-type def) 'org-data)
- ;; Full-fledged definition: footnote ID is
- ;; inserted inside the first parsed paragraph
- ;; (FIRST), if any, to be sure filling will
- ;; take it into consideration.
- (let ((first (car (org-element-contents def))))
- (if (not (eq (org-element-type first) 'paragraph))
- (concat id "\n" (org-export-data def info))
- (push id (nthcdr 2 first))
- (org-export-data def info)))
- ;; Fill paragraph once footnote ID is inserted
- ;; in order to have a correct length for first
- ;; line.
- (org-ascii--fill-string
- (concat id (org-export-data def info))
- text-width info))))))
- definitions "\n\n"))))))
- org-ascii-global-margin)))
+ (let ((global-margin (plist-get info :ascii-global-margin)))
+ (org-ascii--indent-string
+ (concat
+ ;; 1. Document's body.
+ contents
+ ;; 2. Footnote definitions.
+ (let ((definitions (org-export-collect-footnote-definitions info))
+ ;; Insert full links right inside the footnote definition
+ ;; as they have no chance to be inserted later.
+ (info (org-combine-plists info '(:ascii-links-to-notes nil))))
+ (when definitions
+ (concat
+ "\n\n\n"
+ (let ((title (org-ascii--translate "Footnotes" info)))
+ (concat
+ title "\n"
+ (make-string
+ (string-width title)
+ (if (eq (plist-get info :ascii-charset) 'utf-8) ?─ ?_))))
+ "\n\n"
+ (let ((text-width (- (plist-get info :ascii-text-width)
+ global-margin)))
+ (mapconcat
+ (lambda (ref)
+ (let ((id (format "[%s] " (car ref))))
+ ;; Distinguish between inline definitions and
+ ;; full-fledged definitions.
+ (org-trim
+ (let ((def (nth 2 ref)))
+ (if (org-element-map def org-element-all-elements
+ #'identity info 'first-match)
+ ;; Full-fledged definition: footnote ID is
+ ;; inserted inside the first parsed
+ ;; paragraph (FIRST), if any, to be sure
+ ;; filling will take it into consideration.
+ (let ((first (car (org-element-contents def))))
+ (if (not (eq (org-element-type first) 'paragraph))
+ (concat id "\n" (org-export-data def info))
+ (push id (nthcdr 2 first))
+ (org-export-data def info)))
+ ;; Fill paragraph once footnote ID is inserted
+ ;; in order to have a correct length for first
+ ;; line.
+ (org-ascii--fill-string
+ (concat id (org-export-data def info))
+ text-width info))))))
+ definitions "\n\n"))))))
+ global-margin))))
(defun org-ascii-template (contents info)
"Return complete document string after ASCII conversion.
CONTENTS is the transcoded contents string. INFO is a plist
holding export options."
- (concat
- ;; 1. Build title block.
- (org-ascii--indent-string
- (concat (org-ascii-template--document-title info)
- ;; 2. Table of contents.
- (let ((depth (plist-get info :with-toc)))
- (when depth
- (concat
- (org-ascii--build-toc info (and (wholenump depth) depth))
- "\n\n\n"))))
- org-ascii-global-margin)
- ;; 3. Document's body.
- contents
- ;; 4. Creator. Ignore `comment' value as there are no comments in
- ;; ASCII. Justify it to the bottom right.
- (org-ascii--indent-string
- (let ((creator-info (plist-get info :with-creator))
- (text-width (- org-ascii-text-width org-ascii-global-margin)))
- (unless (or (not creator-info) (eq creator-info 'comment))
- (concat
- "\n\n\n"
- (org-ascii--fill-string
- (plist-get info :creator) text-width info 'right))))
- org-ascii-global-margin)))
+ (let ((global-margin (plist-get info :ascii-global-margin)))
+ (concat
+ ;; Build title block.
+ (org-ascii--indent-string
+ (concat (org-ascii-template--document-title info)
+ ;; 2. Table of contents.
+ (let ((depth (plist-get info :with-toc)))
+ (when depth
+ (concat
+ (org-ascii--build-toc info (and (wholenump depth) depth))
+ "\n\n\n"))))
+ global-margin)
+ ;; Document's body.
+ contents
+ ;; Creator. Justify it to the bottom right.
+ (and (plist-get info :with-creator)
+ (org-ascii--indent-string
+ (let ((text-width
+ (- (plist-get info :ascii-text-width) global-margin)))
+ (concat
+ "\n\n\n"
+ (org-ascii--fill-string
+ (plist-get info :creator) text-width info 'right)))
+ global-margin)))))
(defun org-ascii--translate (s info)
"Translate string S according to specified language and charset.
@@ -1007,7 +1168,7 @@ INFO is a plist used as a communication channel."
;;;; Bold
-(defun org-ascii-bold (bold contents info)
+(defun org-ascii-bold (_bold contents _info)
"Transcode BOLD from Org to ASCII.
CONTENTS is the text with bold markup. INFO is a plist holding
contextual information."
@@ -1016,39 +1177,41 @@ contextual information."
;;;; Center Block
-(defun org-ascii-center-block (center-block contents info)
+(defun org-ascii-center-block (_center-block contents _info)
"Transcode a CENTER-BLOCK element from Org to ASCII.
CONTENTS holds the contents of the block. INFO is a plist
holding contextual information."
- (org-ascii--justify-string
- contents (org-ascii--current-text-width center-block info) 'center))
+ ;; Center has already been taken care of at a lower level, so
+ ;; there's nothing left to do.
+ contents)
;;;; Clock
-(defun org-ascii-clock (clock contents info)
+(defun org-ascii-clock (clock _contents info)
"Transcode a CLOCK object from Org to ASCII.
CONTENTS is nil. INFO is a plist holding contextual
information."
- (concat org-clock-string " "
- (org-translate-time
- (org-element-property :raw-value
- (org-element-property :value clock)))
- (let ((time (org-element-property :duration clock)))
- (and time
- (concat " => "
- (apply 'format
- "%2s:%02s"
- (org-split-string time ":")))))))
+ (org-ascii--justify-element
+ (concat org-clock-string " "
+ (org-timestamp-translate (org-element-property :value clock))
+ (let ((time (org-element-property :duration clock)))
+ (and time
+ (concat " => "
+ (apply 'format
+ "%2s:%02s"
+ (org-split-string time ":"))))))
+ clock info))
;;;; Code
-(defun org-ascii-code (code contents info)
+(defun org-ascii-code (code _contents info)
"Return a CODE object from Org to ASCII.
CONTENTS is nil. INFO is a plist holding contextual
information."
- (format org-ascii-verbatim-format (org-element-property :value code)))
+ (format (plist-get info :ascii-verbatim-format)
+ (org-element-property :value code)))
;;;; Drawer
@@ -1059,12 +1222,13 @@ CONTENTS holds the contents of the block. INFO is a plist
holding contextual information."
(let ((name (org-element-property :drawer-name drawer))
(width (org-ascii--current-text-width drawer info)))
- (funcall org-ascii-format-drawer-function name contents width)))
+ (funcall (plist-get info :ascii-format-drawer-function)
+ name contents width)))
;;;; Dynamic Block
-(defun org-ascii-dynamic-block (dynamic-block contents info)
+(defun org-ascii-dynamic-block (_dynamic-block contents _info)
"Transcode a DYNAMIC-BLOCK element from Org to ASCII.
CONTENTS holds the contents of the block. INFO is a plist
holding contextual information."
@@ -1073,7 +1237,7 @@ holding contextual information."
;;;; Entity
-(defun org-ascii-entity (entity contents info)
+(defun org-ascii-entity (entity _contents info)
"Transcode an ENTITY object from Org to ASCII.
CONTENTS are the definition itself. INFO is a plist holding
contextual information."
@@ -1084,16 +1248,18 @@ contextual information."
;;;; Example Block
-(defun org-ascii-example-block (example-block contents info)
+(defun org-ascii-example-block (example-block _contents info)
"Transcode a EXAMPLE-BLOCK element from Org to ASCII.
CONTENTS is nil. INFO is a plist holding contextual information."
- (org-ascii--box-string
- (org-export-format-code-default example-block info) info))
+ (org-ascii--justify-element
+ (org-ascii--box-string
+ (org-export-format-code-default example-block info) info)
+ example-block info))
;;;; Export Snippet
-(defun org-ascii-export-snippet (export-snippet contents info)
+(defun org-ascii-export-snippet (export-snippet _contents _info)
"Transcode a EXPORT-SNIPPET object from Org to ASCII.
CONTENTS is nil. INFO is a plist holding contextual information."
(when (eq (org-export-snippet-backend export-snippet) 'ascii)
@@ -1102,21 +1268,24 @@ CONTENTS is nil. INFO is a plist holding contextual information."
;;;; Export Block
-(defun org-ascii-export-block (export-block contents info)
+(defun org-ascii-export-block (export-block _contents info)
"Transcode a EXPORT-BLOCK element from Org to ASCII.
CONTENTS is nil. INFO is a plist holding contextual information."
(when (string= (org-element-property :type export-block) "ASCII")
- (org-remove-indentation (org-element-property :value export-block))))
+ (org-ascii--justify-element
+ (org-element-property :value export-block) export-block info)))
;;;; Fixed Width
-(defun org-ascii-fixed-width (fixed-width contents info)
+(defun org-ascii-fixed-width (fixed-width _contents info)
"Transcode a FIXED-WIDTH element from Org to ASCII.
CONTENTS is nil. INFO is a plist holding contextual information."
- (org-ascii--box-string
- (org-remove-indentation
- (org-element-property :value fixed-width)) info))
+ (org-ascii--justify-element
+ (org-ascii--box-string
+ (org-remove-indentation
+ (org-element-property :value fixed-width)) info)
+ fixed-width info))
;;;; Footnote Definition
@@ -1127,7 +1296,7 @@ CONTENTS is nil. INFO is a plist holding contextual information."
;;;; Footnote Reference
-(defun org-ascii-footnote-reference (footnote-reference contents info)
+(defun org-ascii-footnote-reference (footnote-reference _contents info)
"Transcode a FOOTNOTE-REFERENCE element from Org to ASCII.
CONTENTS is nil. INFO is a plist holding contextual information."
(format "[%s]" (org-export-get-footnote-number footnote-reference info)))
@@ -1142,57 +1311,62 @@ holding contextual information."
;; Don't export footnote section, which will be handled at the end
;; of the template.
(unless (org-element-property :footnote-section-p headline)
- (let* ((low-level-rank (org-export-low-level-p headline info))
+ (let* ((low-level (org-export-low-level-p headline info))
(width (org-ascii--current-text-width headline info))
+ ;; Export title early so that any link in it can be
+ ;; exported and seen in `org-ascii--unique-links'.
+ (title (org-ascii--build-title headline info width (not low-level)))
;; Blank lines between headline and its contents.
;; `org-ascii-headline-spacing', when set, overwrites
;; original buffer's spacing.
(pre-blanks
- (make-string
- (if org-ascii-headline-spacing (car org-ascii-headline-spacing)
- (org-element-property :pre-blank headline)) ?\n))
- ;; Even if HEADLINE has no section, there might be some
- ;; links in its title that we shouldn't forget to describe.
- (links
- (unless (or (eq (caar (org-element-contents headline)) 'section))
- (let ((title (org-element-property :title headline)))
- (when (consp title)
- (org-ascii--describe-links
- (org-ascii--unique-links title info) width info))))))
+ (make-string (or (car (plist-get info :ascii-headline-spacing))
+ (org-element-property :pre-blank headline)
+ 0)
+ ?\n))
+ (links (and (plist-get info :ascii-links-to-notes)
+ (org-ascii--describe-links
+ (org-ascii--unique-links headline info) width info)))
+ ;; Re-build contents, inserting section links at the right
+ ;; place. The cost is low since build results are cached.
+ (body
+ (if (not (org-string-nw-p links)) contents
+ (let* ((contents (org-element-contents headline))
+ (section (let ((first (car contents)))
+ (and (eq (org-element-type first) 'section)
+ first))))
+ (concat (and section
+ (concat (org-element-normalize-string
+ (org-export-data section info))
+ "\n\n"))
+ links
+ (mapconcat (lambda (e) (org-export-data e info))
+ (if section (cdr contents) contents)
+ ""))))))
;; Deep subtree: export it as a list item.
- (if low-level-rank
- (concat
- ;; Bullet.
- (let ((bullets (cdr (assq (plist-get info :ascii-charset)
- org-ascii-bullets))))
- (char-to-string
- (nth (mod (1- low-level-rank) (length bullets)) bullets)))
- " "
- ;; Title.
- (org-ascii--build-title headline info width) "\n"
- ;; Contents, indented by length of bullet.
- pre-blanks
- (org-ascii--indent-string
- (concat contents
- (when (org-string-nw-p links) (concat "\n\n" links)))
- 2))
+ (if low-level
+ (let* ((bullets (cdr (assq (plist-get info :ascii-charset)
+ (plist-get info :ascii-bullets))))
+ (bullet
+ (format "%c "
+ (nth (mod (1- low-level) (length bullets)) bullets))))
+ (concat bullet title "\n" pre-blanks
+ ;; Contents, indented by length of bullet.
+ (org-ascii--indent-string body (length bullet))))
;; Else: Standard headline.
- (concat
- (org-ascii--build-title headline info width 'underline)
- "\n" pre-blanks
- (concat (when (org-string-nw-p links) links) contents))))))
+ (concat title "\n" pre-blanks body)))))
;;;; Horizontal Rule
-(defun org-ascii-horizontal-rule (horizontal-rule contents info)
+(defun org-ascii-horizontal-rule (horizontal-rule _contents info)
"Transcode an HORIZONTAL-RULE object from Org to ASCII.
CONTENTS is nil. INFO is a plist holding contextual
information."
(let ((text-width (org-ascii--current-text-width horizontal-rule info))
(spec-width
(org-export-read-attribute :attr_ascii horizontal-rule :width)))
- (org-ascii--justify-string
+ (org-ascii--justify-lines
(make-string (if (and spec-width (string-match "^[0-9]+$" spec-width))
(string-to-number spec-width)
text-width)
@@ -1202,23 +1376,23 @@ information."
;;;; Inline Src Block
-(defun org-ascii-inline-src-block (inline-src-block contents info)
+(defun org-ascii-inline-src-block (inline-src-block _contents info)
"Transcode an INLINE-SRC-BLOCK element from Org to ASCII.
CONTENTS holds the contents of the item. INFO is a plist holding
contextual information."
- (format org-ascii-verbatim-format
+ (format (plist-get info :ascii-verbatim-format)
(org-element-property :value inline-src-block)))
;;;; Inlinetask
(defun org-ascii-format-inlinetask-default
- (todo type priority name tags contents width inlinetask info)
+ (_todo _type _priority _name _tags contents width inlinetask info)
"Format an inline task element for ASCII export.
See `org-ascii-format-inlinetask-function' for a description
of the parameters."
(let* ((utf8p (eq (plist-get info :ascii-charset) 'utf-8))
- (width (or width org-ascii-inlinetask-width)))
+ (width (or width (plist-get info :ascii-inlinetask-width))))
(org-ascii--indent-string
(concat
;; Top line, with an additional blank line if not in UTF-8.
@@ -1236,9 +1410,9 @@ of the parameters."
;; Bottom line.
(make-string width (if utf8p ?━ ?_)))
;; Flush the inlinetask to the right.
- (- org-ascii-text-width org-ascii-global-margin
+ (- (plist-get info :ascii-text-width) (plist-get info :ascii-global-margin)
(if (not (org-export-get-parent-headline inlinetask)) 0
- org-ascii-inner-margin)
+ (plist-get info :ascii-inner-margin))
(org-ascii--current-text-width inlinetask info)))))
(defun org-ascii-inlinetask (inlinetask contents info)
@@ -1246,7 +1420,7 @@ of the parameters."
CONTENTS holds the contents of the block. INFO is a plist
holding contextual information."
(let ((width (org-ascii--current-text-width inlinetask info)))
- (funcall org-ascii-format-inlinetask-function
+ (funcall (plist-get info :ascii-format-inlinetask-function)
;; todo.
(and (plist-get info :with-todo-keywords)
(let ((todo (org-element-property
@@ -1268,7 +1442,7 @@ holding contextual information."
;;;; Italic
-(defun org-ascii-italic (italic contents info)
+(defun org-ascii-italic (_italic contents _info)
"Transcode italic from Org to ASCII.
CONTENTS is the text with italic markup. INFO is a plist holding
contextual information."
@@ -1288,12 +1462,12 @@ contextual information."
;; First parent of ITEM is always the plain-list. Get
;; `:type' property from it.
(org-list-bullet-string
- (case list-type
- (descriptive
+ (pcase list-type
+ (`descriptive
(concat checkbox
(org-export-data (org-element-property :tag item) info)
": "))
- (ordered
+ (`ordered
;; Return correct number for ITEM, paying attention to
;; counters.
(let* ((struct (org-element-property :structure item))
@@ -1305,7 +1479,7 @@ contextual information."
(org-list-prevs-alist struct)
(org-list-parents-alist struct)))))))
(replace-regexp-in-string "[0-9]+" num bul)))
- (t (let ((bul (org-element-property :bullet item)))
+ (_ (let ((bul (org-element-property :bullet item)))
;; Change bullets into more visible form if UTF-8 is active.
(if (not utf8p) bul
(replace-regexp-in-string
@@ -1327,42 +1501,45 @@ contextual information."
;;;; Keyword
-(defun org-ascii-keyword (keyword contents info)
+(defun org-ascii-keyword (keyword _contents info)
"Transcode a KEYWORD element from Org to ASCII.
CONTENTS is nil. INFO is a plist holding contextual
information."
(let ((key (org-element-property :key keyword))
(value (org-element-property :value keyword)))
(cond
- ((string= key "ASCII") value)
+ ((string= key "ASCII") (org-ascii--justify-element value keyword info))
((string= key "TOC")
- (let ((value (downcase value)))
- (cond
- ((string-match "\\" value)
- (let ((depth (or (and (string-match "[0-9]+" value)
- (string-to-number (match-string 0 value)))
- (plist-get info :with-toc))))
- (org-ascii--build-toc
- info (and (wholenump depth) depth) keyword)))
- ((string= "tables" value)
- (org-ascii--list-tables keyword info))
- ((string= "listings" value)
- (org-ascii--list-listings keyword info))))))))
+ (org-ascii--justify-element
+ (let ((case-fold-search t))
+ (cond
+ ((string-match-p "\\" value)
+ (let ((depth (and (string-match "\\<[0-9]+\\>" value)
+ (string-to-number (match-string 0 value))))
+ (localp (string-match-p "\\" value)))
+ (org-ascii--build-toc info depth keyword localp)))
+ ((string-match-p "\\" value)
+ (org-ascii--list-tables keyword info))
+ ((string-match-p "\\" value)
+ (org-ascii--list-listings keyword info))))
+ keyword info)))))
;;;; Latex Environment
-(defun org-ascii-latex-environment (latex-environment contents info)
+(defun org-ascii-latex-environment (latex-environment _contents info)
"Transcode a LATEX-ENVIRONMENT element from Org to ASCII.
CONTENTS is nil. INFO is a plist holding contextual
information."
(when (plist-get info :with-latex)
- (org-remove-indentation (org-element-property :value latex-environment))))
+ (org-ascii--justify-element
+ (org-remove-indentation (org-element-property :value latex-environment))
+ latex-environment info)))
;;;; Latex Fragment
-(defun org-ascii-latex-fragment (latex-fragment contents info)
+(defun org-ascii-latex-fragment (latex-fragment _contents info)
"Transcode a LATEX-FRAGMENT object from Org to ASCII.
CONTENTS is nil. INFO is a plist holding contextual
information."
@@ -1372,7 +1549,7 @@ information."
;;;; Line Break
-(defun org-ascii-line-break (line-break contents info)
+(defun org-ascii-line-break (_line-break _contents _info)
"Transcode a LINE-BREAK object from Org to ASCII.
CONTENTS is nil. INFO is a plist holding contextual
information." hard-newline)
@@ -1385,9 +1562,9 @@ CONTENTS is nil. INFO is a plist holding contextual
DESC is the description part of the link, or the empty string.
INFO is a plist holding contextual information."
- (let ((raw-link (org-element-property :raw-link link))
- (type (org-element-property :type link)))
+ (let ((type (org-element-property :type link)))
(cond
+ ((org-export-custom-protocol-maybe link desc 'ascii))
((string= type "coderef")
(let ((ref (org-element-property :path link)))
(format (org-export-get-coderef-format ref desc)
@@ -1395,23 +1572,51 @@ INFO is a plist holding contextual information."
;; Do not apply a special syntax on radio links. Though, use
;; transcoded target's contents as output.
((string= type "radio") desc)
- ;; Do not apply a special syntax on fuzzy links pointing to
- ;; targets.
- ((string= type "fuzzy")
- (let ((destination (org-export-resolve-fuzzy-link link info)))
- (if (org-string-nw-p desc) desc
- (when destination
- (let ((number
- (org-export-get-ordinal
- destination info nil 'org-ascii--has-caption-p)))
- (when number
- (if (atom number) (number-to-string number)
- (mapconcat 'number-to-string number "."))))))))
+ ((member type '("custom-id" "fuzzy" "id"))
+ (let ((destination (if (string= type "fuzzy")
+ (org-export-resolve-fuzzy-link link info)
+ (org-export-resolve-id-link link info))))
+ (pcase (org-element-type destination)
+ ((guard desc)
+ (if (plist-get info :ascii-links-to-notes)
+ (format "[%s]" desc)
+ (concat desc
+ (format " (%s)"
+ (org-ascii--describe-datum destination info)))))
+ ;; External file.
+ (`plain-text destination)
+ (`headline
+ (if (org-export-numbered-headline-p destination info)
+ (mapconcat #'number-to-string
+ (org-export-get-headline-number destination info)
+ ".")
+ (org-export-data (org-element-property :title destination) info)))
+ ;; Handle enumerable elements and targets within them.
+ ((and (let number (org-export-get-ordinal
+ destination info nil #'org-ascii--has-caption-p))
+ (guard number))
+ (if (atom number) (number-to-string number)
+ (mapconcat #'number-to-string number ".")))
+ ;; Don't know what to do. Signal it.
+ (_ "???"))))
(t
- (if (not (org-string-nw-p desc)) (format "[%s]" raw-link)
- (concat
- (format "[%s]" desc)
- (unless org-ascii-links-to-notes (format " (%s)" raw-link))))))))
+ (let ((raw-link (org-element-property :raw-link link)))
+ (if (not (org-string-nw-p desc)) (format "[%s]" raw-link)
+ (concat (format "[%s]" desc)
+ (and (not (plist-get info :ascii-links-to-notes))
+ (format " (%s)" raw-link)))))))))
+
+
+;;;; Node Properties
+
+(defun org-ascii-node-property (node-property _contents _info)
+ "Transcode a NODE-PROPERTY element from Org to ASCII.
+CONTENTS is nil. INFO is a plist holding contextual
+information."
+ (format "%s:%s"
+ (org-element-property :key node-property)
+ (let ((value (org-element-property :value node-property)))
+ (if value (concat " " value) ""))))
;;;; Paragraph
@@ -1420,16 +1625,17 @@ INFO is a plist holding contextual information."
"Transcode a PARAGRAPH element from Org to ASCII.
CONTENTS is the contents of the paragraph, as a string. INFO is
the plist used as a communication channel."
- (org-ascii--fill-string
- (if (not (wholenump org-ascii-indented-line-width)) contents
- (concat
- ;; Do not indent first paragraph in a section.
- (unless (and (not (org-export-get-previous-element paragraph info))
- (eq (org-element-type (org-export-get-parent paragraph))
- 'section))
- (make-string org-ascii-indented-line-width ?\s))
- (replace-regexp-in-string "\\`[ \t]+" "" contents)))
- (org-ascii--current-text-width paragraph info) info))
+ (org-ascii--justify-element
+ (let ((indented-line-width (plist-get info :ascii-indented-line-width)))
+ (if (not (wholenump indented-line-width)) contents
+ (concat
+ ;; Do not indent first paragraph in a section.
+ (unless (and (not (org-export-get-previous-element paragraph info))
+ (eq (org-element-type (org-export-get-parent paragraph))
+ 'section))
+ (make-string indented-line-width ?\s))
+ (replace-regexp-in-string "\\`[ \t]+" "" contents))))
+ paragraph info))
;;;; Plain List
@@ -1438,7 +1644,11 @@ the plist used as a communication channel."
"Transcode a PLAIN-LIST element from Org to ASCII.
CONTENTS is the contents of the list. INFO is a plist holding
contextual information."
- contents)
+ (let ((margin (plist-get info :ascii-list-margin)))
+ (if (or (< margin 1)
+ (eq (org-element-type (org-export-get-parent plain-list)) 'item))
+ contents
+ (org-ascii--indent-string contents margin))))
;;;; Plain Text
@@ -1462,62 +1672,52 @@ INFO is a plist used as a communication channel."
;;;; Planning
-(defun org-ascii-planning (planning contents info)
+(defun org-ascii-planning (planning _contents info)
"Transcode a PLANNING element from Org to ASCII.
CONTENTS is nil. INFO is a plist used as a communication
channel."
- (mapconcat
- 'identity
- (delq nil
- (list (let ((closed (org-element-property :closed planning)))
- (when closed
- (concat org-closed-string " "
- (org-translate-time
- (org-element-property :raw-value closed)))))
- (let ((deadline (org-element-property :deadline planning)))
- (when deadline
- (concat org-deadline-string " "
- (org-translate-time
- (org-element-property :raw-value deadline)))))
- (let ((scheduled (org-element-property :scheduled planning)))
- (when scheduled
- (concat org-scheduled-string " "
- (org-translate-time
- (org-element-property :raw-value scheduled)))))))
- " "))
+ (org-ascii--justify-element
+ (mapconcat
+ #'identity
+ (delq nil
+ (list (let ((closed (org-element-property :closed planning)))
+ (when closed
+ (concat org-closed-string " "
+ (org-timestamp-translate closed))))
+ (let ((deadline (org-element-property :deadline planning)))
+ (when deadline
+ (concat org-deadline-string " "
+ (org-timestamp-translate deadline))))
+ (let ((scheduled (org-element-property :scheduled planning)))
+ (when scheduled
+ (concat org-scheduled-string " "
+ (org-timestamp-translate scheduled))))))
+ " ")
+ planning info))
+
+
+;;;; Property Drawer
+
+(defun org-ascii-property-drawer (property-drawer contents info)
+ "Transcode a PROPERTY-DRAWER element from Org to ASCII.
+CONTENTS holds the contents of the drawer. INFO is a plist
+holding contextual information."
+ (and (org-string-nw-p contents)
+ (org-ascii--justify-element contents property-drawer info)))
;;;; Quote Block
-(defun org-ascii-quote-block (quote-block contents info)
+(defun org-ascii-quote-block (_quote-block contents info)
"Transcode a QUOTE-BLOCK element from Org to ASCII.
CONTENTS holds the contents of the block. INFO is a plist
holding contextual information."
- (org-ascii--indent-string contents org-ascii-quote-margin))
-
-
-;;;; Quote Section
-
-(defun org-ascii-quote-section (quote-section contents info)
- "Transcode a QUOTE-SECTION element from Org to ASCII.
-CONTENTS is nil. INFO is a plist holding contextual information."
- (let ((width (org-ascii--current-text-width quote-section info))
- (value
- (org-export-data
- (org-remove-indentation (org-element-property :value quote-section))
- info)))
- (org-ascii--indent-string
- value
- (+ org-ascii-quote-margin
- ;; Don't apply inner margin if parent headline is low level.
- (let ((headline (org-export-get-parent-headline quote-section)))
- (if (org-export-low-level-p headline info) 0
- org-ascii-inner-margin))))))
+ (org-ascii--indent-string contents (plist-get info :ascii-quote-margin)))
;;;; Radio Target
-(defun org-ascii-radio-target (radio-target contents info)
+(defun org-ascii-radio-target (_radio-target contents _info)
"Transcode a RADIO-TARGET object from Org to ASCII.
CONTENTS is the contents of the target. INFO is a plist holding
contextual information."
@@ -1530,50 +1730,56 @@ contextual information."
"Transcode a SECTION element from Org to ASCII.
CONTENTS is the contents of the section. INFO is a plist holding
contextual information."
- (org-ascii--indent-string
- (concat
- contents
- (when org-ascii-links-to-notes
- ;; Add list of links at the end of SECTION.
- (let ((links (org-ascii--describe-links
- (org-ascii--unique-links section info)
- (org-ascii--current-text-width section info) info)))
- ;; Separate list of links and section contents.
- (when (org-string-nw-p links) (concat "\n\n" links)))))
- ;; Do not apply inner margin if parent headline is low level.
- (let ((headline (org-export-get-parent-headline section)))
- (if (or (not headline) (org-export-low-level-p headline info)) 0
- org-ascii-inner-margin))))
+ (let ((links
+ (and (plist-get info :ascii-links-to-notes)
+ ;; Take care of links in first section of the document.
+ (not (org-element-lineage section '(headline)))
+ (org-ascii--describe-links
+ (org-ascii--unique-links section info)
+ (org-ascii--current-text-width section info)
+ info))))
+ (org-ascii--indent-string
+ (if (not (org-string-nw-p links)) contents
+ (concat (org-element-normalize-string contents) "\n\n" links))
+ ;; Do not apply inner margin if parent headline is low level.
+ (let ((headline (org-export-get-parent-headline section)))
+ (if (or (not headline) (org-export-low-level-p headline info)) 0
+ (plist-get info :ascii-inner-margin))))))
;;;; Special Block
-(defun org-ascii-special-block (special-block contents info)
+(defun org-ascii-special-block (_special-block contents _info)
"Transcode a SPECIAL-BLOCK element from Org to ASCII.
CONTENTS holds the contents of the block. INFO is a plist
holding contextual information."
+ ;; "JUSTIFYLEFT" and "JUSTIFYRIGHT" have already been taken care of
+ ;; at a lower level. There is no other special block type to
+ ;; handle.
contents)
;;;; Src Block
-(defun org-ascii-src-block (src-block contents info)
+(defun org-ascii-src-block (src-block _contents info)
"Transcode a SRC-BLOCK element from Org to ASCII.
CONTENTS holds the contents of the item. INFO is a plist holding
contextual information."
(let ((caption (org-ascii--build-caption src-block info))
+ (caption-above-p (plist-get info :ascii-caption-above))
(code (org-export-format-code-default src-block info)))
(if (equal code "") ""
- (concat
- (when (and caption org-ascii-caption-above) (concat caption "\n"))
- (org-ascii--box-string code info)
- (when (and caption (not org-ascii-caption-above))
- (concat "\n" caption))))))
+ (org-ascii--justify-element
+ (concat
+ (and caption caption-above-p (concat caption "\n"))
+ (org-ascii--box-string code info)
+ (and caption (not caption-above-p) (concat "\n" caption)))
+ src-block info))))
;;;; Statistics Cookie
-(defun org-ascii-statistics-cookie (statistics-cookie contents info)
+(defun org-ascii-statistics-cookie (statistics-cookie _contents _info)
"Transcode a STATISTICS-COOKIE object from Org to ASCII.
CONTENTS is nil. INFO is a plist holding contextual information."
(org-element-property :value statistics-cookie))
@@ -1581,7 +1787,7 @@ CONTENTS is nil. INFO is a plist holding contextual information."
;;;; Subscript
-(defun org-ascii-subscript (subscript contents info)
+(defun org-ascii-subscript (subscript contents _info)
"Transcode a SUBSCRIPT object from Org to ASCII.
CONTENTS is the contents of the object. INFO is a plist holding
contextual information."
@@ -1592,7 +1798,7 @@ contextual information."
;;;; Superscript
-(defun org-ascii-superscript (superscript contents info)
+(defun org-ascii-superscript (superscript contents _info)
"Transcode a SUPERSCRIPT object from Org to ASCII.
CONTENTS is the contents of the object. INFO is a plist holding
contextual information."
@@ -1603,7 +1809,7 @@ contextual information."
;;;; Strike-through
-(defun org-ascii-strike-through (strike-through contents info)
+(defun org-ascii-strike-through (_strike-through contents _info)
"Transcode STRIKE-THROUGH from Org to ASCII.
CONTENTS is text with strike-through markup. INFO is a plist
holding contextual information."
@@ -1616,26 +1822,29 @@ holding contextual information."
"Transcode a TABLE element from Org to ASCII.
CONTENTS is the contents of the table. INFO is a plist holding
contextual information."
- (let ((caption (org-ascii--build-caption table info)))
- (concat
- ;; Possibly add a caption string above.
- (when (and caption org-ascii-caption-above) (concat caption "\n"))
- ;; Insert table. Note: "table.el" tables are left unmodified.
- (cond ((eq (org-element-property :type table) 'org) contents)
- ((and org-ascii-table-use-ascii-art
- (eq (plist-get info :ascii-charset) 'utf-8)
- (require 'ascii-art-to-unicode nil t))
- (with-temp-buffer
- (insert (org-remove-indentation
- (org-element-property :value table)))
- (goto-char (point-min))
- (aa2u)
- (goto-char (point-max))
- (skip-chars-backward " \r\t\n")
- (buffer-substring (point-min) (point))))
- (t (org-remove-indentation (org-element-property :value table))))
- ;; Possible add a caption string below.
- (and (not org-ascii-caption-above) caption))))
+ (let ((caption (org-ascii--build-caption table info))
+ (caption-above-p (plist-get info :ascii-caption-above)))
+ (org-ascii--justify-element
+ (concat
+ ;; Possibly add a caption string above.
+ (and caption caption-above-p (concat caption "\n"))
+ ;; Insert table. Note: "table.el" tables are left unmodified.
+ (cond ((eq (org-element-property :type table) 'org) contents)
+ ((and (plist-get info :ascii-table-use-ascii-art)
+ (eq (plist-get info :ascii-charset) 'utf-8)
+ (require 'ascii-art-to-unicode nil t))
+ (with-temp-buffer
+ (insert (org-remove-indentation
+ (org-element-property :value table)))
+ (goto-char (point-min))
+ (aa2u)
+ (goto-char (point-max))
+ (skip-chars-backward " \r\t\n")
+ (buffer-substring (point-min) (point))))
+ (t (org-remove-indentation (org-element-property :value table))))
+ ;; Possible add a caption string below.
+ (and (not caption-above-p) caption))
+ table info)))
;;;; Table Cell
@@ -1661,12 +1870,13 @@ are ignored."
(plist-put info :ascii-table-cell-width-cache
(make-hash-table :test 'equal)))
:ascii-table-cell-width-cache)))
- (key (cons table col)))
+ (key (cons table col))
+ (widenp (plist-get info :ascii-table-widen-columns)))
(or (gethash key cache)
(puthash
key
(let ((cookie-width (org-export-table-cell-width table-cell info)))
- (or (and (not org-ascii-table-widen-columns) cookie-width)
+ (or (and (not widenp) cookie-width)
(let ((contents-width
(let ((max-width 0))
(org-element-map table 'table-row
@@ -1681,8 +1891,7 @@ are ignored."
info)
max-width)))
(cond ((not cookie-width) contents-width)
- (org-ascii-table-widen-columns
- (max cookie-width contents-width))
+ (widenp (max cookie-width contents-width))
(t cookie-width)))))
cache))))
@@ -1696,14 +1905,14 @@ a communication channel."
;; each cell in the column.
(let ((width (org-ascii--table-cell-width table-cell info)))
;; When contents are too large, truncate them.
- (unless (or org-ascii-table-widen-columns
+ (unless (or (plist-get info :ascii-table-widen-columns)
(<= (string-width (or contents "")) width))
(setq contents (concat (substring contents 0 (- width 2)) "=>")))
;; Align contents correctly within the cell.
(let* ((indent-tabs-mode nil)
(data
(when contents
- (org-ascii--justify-string
+ (org-ascii--justify-lines
contents width
(org-export-table-cell-alignment table-cell info)))))
(setq contents
@@ -1770,7 +1979,7 @@ a communication channel."
;;;; Timestamp
-(defun org-ascii-timestamp (timestamp contents info)
+(defun org-ascii-timestamp (timestamp _contents info)
"Transcode a TIMESTAMP object from Org to ASCII.
CONTENTS is nil. INFO is a plist holding contextual information."
(org-ascii-plain-text (org-timestamp-translate timestamp) info))
@@ -1778,7 +1987,7 @@ CONTENTS is nil. INFO is a plist holding contextual information."
;;;; Underline
-(defun org-ascii-underline (underline contents info)
+(defun org-ascii-underline (_underline contents _info)
"Transcode UNDERLINE from Org to ASCII.
CONTENTS is the text with underline markup. INFO is a plist
holding contextual information."
@@ -1787,10 +1996,10 @@ holding contextual information."
;;;; Verbatim
-(defun org-ascii-verbatim (verbatim contents info)
+(defun org-ascii-verbatim (verbatim _contents info)
"Return a VERBATIM object from Org to ASCII.
CONTENTS is nil. INFO is a plist holding contextual information."
- (format org-ascii-verbatim-format
+ (format (plist-get info :ascii-verbatim-format)
(org-element-property :value verbatim)))
@@ -1800,48 +2009,48 @@ CONTENTS is nil. INFO is a plist holding contextual information."
"Transcode a VERSE-BLOCK element from Org to ASCII.
CONTENTS is verse block contents. INFO is a plist holding
contextual information."
- (let ((verse-width (org-ascii--current-text-width verse-block info)))
- (org-ascii--indent-string
- (org-ascii--justify-string contents verse-width 'left)
- org-ascii-quote-margin)))
+ (org-ascii--indent-string
+ (org-ascii--justify-element contents verse-block info)
+ (plist-get info :ascii-quote-margin)))
;;; Filters
-(defun org-ascii-filter-headline-blank-lines (headline back-end info)
+(defun org-ascii-filter-headline-blank-lines (headline _backend info)
"Filter controlling number of blank lines after a headline.
-HEADLINE is a string representing a transcoded headline.
-BACK-END is symbol specifying back-end used for export. INFO is
-plist containing the communication channel.
+HEADLINE is a string representing a transcoded headline. BACKEND
+is symbol specifying back-end used for export. INFO is plist
+containing the communication channel.
This function only applies to `ascii' back-end. See
`org-ascii-headline-spacing' for information."
- (if (not org-ascii-headline-spacing) headline
- (let ((blanks (make-string (1+ (cdr org-ascii-headline-spacing)) ?\n)))
- (replace-regexp-in-string "\n\\(?:\n[ \t]*\\)*\\'" blanks headline))))
+ (let ((headline-spacing (plist-get info :ascii-headline-spacing)))
+ (if (not headline-spacing) headline
+ (let ((blanks (make-string (1+ (cdr headline-spacing)) ?\n)))
+ (replace-regexp-in-string "\n\\(?:\n[ \t]*\\)*\\'" blanks headline)))))
-(defun org-ascii-filter-paragraph-spacing (tree back-end info)
+(defun org-ascii-filter-paragraph-spacing (tree _backend info)
"Filter controlling number of blank lines between paragraphs.
-TREE is the parse tree. BACK-END is the symbol specifying
+TREE is the parse tree. BACKEND is the symbol specifying
back-end used for export. INFO is a plist used as
a communication channel.
See `org-ascii-paragraph-spacing' for information."
- (when (wholenump org-ascii-paragraph-spacing)
- (org-element-map tree 'paragraph
- (lambda (p)
- (when (eq (org-element-type (org-export-get-next-element p info))
- 'paragraph)
- (org-element-put-property
- p :post-blank org-ascii-paragraph-spacing)))))
+ (let ((paragraph-spacing (plist-get info :ascii-paragraph-spacing)))
+ (when (wholenump paragraph-spacing)
+ (org-element-map tree 'paragraph
+ (lambda (p)
+ (when (eq (org-element-type (org-export-get-next-element p info))
+ 'paragraph)
+ (org-element-put-property p :post-blank paragraph-spacing))))))
tree)
-(defun org-ascii-filter-comment-spacing (tree backend info)
+(defun org-ascii-filter-comment-spacing (tree _backend info)
"Filter removing blank lines between comments.
-TREE is the parse tree. BACK-END is the symbol specifying
+TREE is the parse tree. BACKEND is the symbol specifying
back-end used for export. INFO is a plist used as
a communication channel."
(org-element-map tree '(comment comment-block)
diff --git a/lisp/org/ox-beamer.el b/lisp/org/ox-beamer.el
index a8d48b67189..82651d3848e 100644
--- a/lisp/org/ox-beamer.el
+++ b/lisp/org/ox-beamer.el
@@ -1,4 +1,4 @@
-;;; ox-beamer.el --- Beamer Back-End for Org Export Engine
+;;; ox-beamer.el --- Beamer Back-End for Org Export Engine -*- lexical-binding: t; -*-
;; Copyright (C) 2007-2017 Free Software Foundation, Inc.
@@ -29,7 +29,7 @@
;;; Code:
-(eval-when-compile (require 'cl))
+(require 'cl-lib)
(require 'ox-latex)
;; Install a default set-up for Beamer export.
@@ -105,7 +105,9 @@ key Selection key for `org-beamer-select-environment'
open The opening template for the environment, with the following escapes
%a the action/overlay specification
%A the default action/overlay specification
- %o the options argument of the template
+ %R the raw BEAMER_act value
+ %o the options argument, with square brackets
+ %O the raw BEAMER_opt value
%h the headline text
%r the raw headline text (i.e. without any processing)
%H if there is headline text, that raw text in {} braces
@@ -133,6 +135,15 @@ You might want to put e.g. \"allowframebreaks=0.9\" here."
:type '(string :tag "Outline frame options"))
+(defcustom org-beamer-subtitle-format "\\subtitle{%s}"
+ "Format string used for transcoded subtitle.
+The format string should have at most one \"%s\"-expression,
+which is replaced with the subtitle."
+ :group 'org-export-beamer
+ :version "26.1"
+ :package-version '(Org . "8.3")
+ :type '(string :tag "Format string"))
+
;;; Internal Variables
@@ -191,19 +202,14 @@ TYPE is a symbol among the following:
`defaction' Return ARGUMENT within both square and angular brackets.
`option' Return ARGUMENT within square brackets."
(if (not (string-match "\\S-" argument)) ""
- (case type
- (action (if (string-match "\\`<.*>\\'" argument) argument
- (format "<%s>" argument)))
- (defaction (cond
- ((string-match "\\`\\[<.*>\\]\\'" argument) argument)
- ((string-match "\\`<.*>\\'" argument)
- (format "[%s]" argument))
- ((string-match "\\`\\[\\(.*\\)\\]\\'" argument)
- (format "[<%s>]" (match-string 1 argument)))
- (t (format "[<%s>]" argument))))
- (option (if (string-match "\\`\\[.*\\]\\'" argument) argument
- (format "[%s]" argument)))
- (otherwise argument))))
+ (cl-case type
+ (action (format "<%s>" (org-unbracket-string "<" ">" argument)))
+ (defaction
+ (format "[<%s>]"
+ (org-unbracket-string "<" ">" (org-unbracket-string "[" "]" argument))))
+ (option (format "[%s]" (org-unbracket-string "[" "]" argument)))
+ (otherwise (error "Invalid `type' argument to `org-beamer--normalize-argument': %s"
+ type)))))
(defun org-beamer--element-has-overlay-p (element)
"Non-nil when ELEMENT has an overlay specified.
@@ -213,14 +219,14 @@ Return overlay specification, as a string, or nil."
(let ((first-object (car (org-element-contents element))))
(when (eq (org-element-type first-object) 'export-snippet)
(let ((value (org-element-property :value first-object)))
- (and (string-match "\\`<.*>\\'" value) value)))))
+ (and (string-prefix-p "<" value) (string-suffix-p ">" value)
+ value)))))
;;; Define Back-End
(org-export-define-derived-backend 'beamer 'latex
- :export-block "BEAMER"
:menu-entry
'(?l 1
((?B "As LaTeX buffer (Beamer)" org-beamer-export-as-latex)
@@ -231,15 +237,20 @@ Return overlay specification, as a string, or nil."
(if a (org-beamer-export-to-pdf t s v b)
(org-open-file (org-beamer-export-to-pdf nil s v b)))))))
:options-alist
- '((:beamer-theme "BEAMER_THEME" nil org-beamer-theme)
+ '((:headline-levels nil "H" org-beamer-frame-level)
+ (:latex-class "LATEX_CLASS" nil "beamer" t)
+ (:beamer-subtitle-format nil nil org-beamer-subtitle-format)
+ (:beamer-column-view-format "COLUMNS" nil org-beamer-column-view-format)
+ (:beamer-theme "BEAMER_THEME" nil org-beamer-theme)
(:beamer-color-theme "BEAMER_COLOR_THEME" nil nil t)
(:beamer-font-theme "BEAMER_FONT_THEME" nil nil t)
(:beamer-inner-theme "BEAMER_INNER_THEME" nil nil t)
(:beamer-outer-theme "BEAMER_OUTER_THEME" nil nil t)
- (:beamer-header-extra "BEAMER_HEADER" nil nil newline)
- ;; Modify existing properties.
- (:headline-levels nil "H" org-beamer-frame-level)
- (:latex-class "LATEX_CLASS" nil "beamer" t))
+ (:beamer-header "BEAMER_HEADER" nil nil newline)
+ (:beamer-environments-extra nil nil org-beamer-environments-extra)
+ (:beamer-frame-default-options nil nil org-beamer-frame-default-options)
+ (:beamer-outline-frame-options nil nil org-beamer-outline-frame-options)
+ (:beamer-outline-frame-title nil nil org-beamer-outline-frame-title))
:translate-alist '((bold . org-beamer-bold)
(export-block . org-beamer-export-block)
(export-snippet . org-beamer-export-snippet)
@@ -249,7 +260,6 @@ Return overlay specification, as a string, or nil."
(link . org-beamer-link)
(plain-list . org-beamer-plain-list)
(radio-target . org-beamer-radio-target)
- (target . org-beamer-target)
(template . org-beamer-template)))
@@ -258,7 +268,7 @@ Return overlay specification, as a string, or nil."
;;;; Bold
-(defun org-beamer-bold (bold contents info)
+(defun org-beamer-bold (bold contents _info)
"Transcode BLOCK object into Beamer code.
CONTENTS is the text being bold. INFO is a plist used as
a communication channel."
@@ -269,7 +279,7 @@ a communication channel."
;;;; Export Block
-(defun org-beamer-export-block (export-block contents info)
+(defun org-beamer-export-block (export-block _contents _info)
"Transcode an EXPORT-BLOCK element into Beamer code.
CONTENTS is nil. INFO is a plist used as a communication
channel."
@@ -279,7 +289,7 @@ channel."
;;;; Export Snippet
-(defun org-beamer-export-snippet (export-snippet contents info)
+(defun org-beamer-export-snippet (export-snippet _contents info)
"Transcode an EXPORT-SNIPPET object into Beamer code.
CONTENTS is nil. INFO is a plist used as a communication
channel."
@@ -315,16 +325,21 @@ channel."
INFO is a plist used as a communication channel.
The value is either the label specified in \"BEAMER_opt\"
-property, or a fallback value built from headline's number. This
-function assumes HEADLINE will be treated as a frame."
- (let ((opt (org-element-property :BEAMER_OPT headline)))
- (if (and (org-string-nw-p opt)
- (string-match "\\(?:^\\|,\\)label=\\(.*?\\)\\(?:$\\|,\\)" opt))
- (match-string 1 opt)
- (format "sec-%s"
- (mapconcat 'number-to-string
- (org-export-get-headline-number headline info)
- "-")))))
+property, the custom ID, if there is one and
+`:latex-prefer-user-labels' property has a non nil value, or
+a unique internal label. This function assumes HEADLINE will be
+treated as a frame."
+ (cond
+ ((let ((opt (org-element-property :BEAMER_OPT headline)))
+ (and (stringp opt)
+ (string-match "\\(?:^\\|,\\)label=\\(.*?\\)\\(?:$\\|,\\)" opt)
+ (let ((label (match-string 1 opt)))
+ (if (string-match-p "\\`{.*}\\'" label)
+ (substring label 1 -1)
+ label)))))
+ ((and (plist-get info :latex-prefer-user-labels)
+ (org-element-property :CUSTOM_ID headline)))
+ (t (format "sec:%s" (org-export-get-reference headline info)))))
(defun org-beamer--frame-level (headline info)
"Return frame level in subtree containing HEADLINE.
@@ -333,12 +348,10 @@ INFO is a plist used as a communication channel."
;; 1. Look for "frame" environment in parents, starting from the
;; farthest.
(catch 'exit
- (mapc (lambda (parent)
- (let ((env (org-element-property :BEAMER_ENV parent)))
- (when (and env (member-ignore-case env '("frame" "fullframe")))
- (throw 'exit (org-export-get-relative-level parent info)))))
- (nreverse (org-export-get-genealogy headline)))
- nil)
+ (dolist (parent (nreverse (org-element-lineage headline)))
+ (let ((env (org-element-property :BEAMER_ENV parent)))
+ (when (and env (member-ignore-case env '("frame" "fullframe")))
+ (throw 'exit (org-export-get-relative-level parent info))))))
;; 2. Look for "frame" environment in HEADLINE.
(let ((env (org-element-property :BEAMER_ENV headline)))
(and env (member-ignore-case env '("frame" "fullframe"))
@@ -413,7 +426,8 @@ used as a communication channel."
;; Collect options from default value and headline's
;; properties. Also add a label for links.
(append
- (org-split-string org-beamer-frame-default-options ",")
+ (org-split-string
+ (plist-get info :beamer-frame-default-options) ",")
(and beamer-opt
(org-split-string
;; Remove square brackets if user provided
@@ -422,12 +436,20 @@ used as a communication channel."
(match-string 1 beamer-opt))
","))
;; Provide an automatic label for the frame
- ;; unless the user specified one.
+ ;; unless the user specified one. Also refrain
+ ;; from labeling `allowframebreaks' frames; this
+ ;; is not allowed by beamer.
(unless (and beamer-opt
- (string-match "\\(^\\|,\\)label=" beamer-opt))
+ (or (string-match "\\(^\\|,\\)label=" beamer-opt)
+ (string-match "allowframebreaks" beamer-opt)))
(list
- (format "label=%s"
- (org-beamer--get-label headline info)))))))
+ (let ((label (org-beamer--get-label headline info)))
+ ;; Labels containing colons need to be
+ ;; wrapped within braces.
+ (format (if (string-match-p ":" label)
+ "label={%s}"
+ "label=%s")
+ label)))))))
;; Change options list into a string.
(org-beamer--normalize-argument
(mapconcat
@@ -475,14 +497,15 @@ used as a communication channel."
(env-format
(cond ((member environment '("column" "columns")) nil)
((assoc environment
- (append org-beamer-environments-extra
+ (append (plist-get info :beamer-environments-extra)
org-beamer-environments-default)))
(t (user-error "Wrong block type at a headline named \"%s\""
raw-title))))
(title (org-export-data (org-element-property :title headline) info))
- (options (let ((options (org-element-property :BEAMER_OPT headline)))
- (if (not options) ""
- (org-beamer--normalize-argument options 'option))))
+ (raw-options (org-element-property :BEAMER_OPT headline))
+ (options (if raw-options
+ (org-beamer--normalize-argument raw-options 'option)
+ ""))
;; Start a "columns" environment when explicitly requested or
;; when there is no previous headline or the previous
;; headline do not have a BEAMER_column property.
@@ -521,7 +544,7 @@ used as a communication channel."
;; One can specify placement for column only when
;; HEADLINE stands for a column on its own.
(if (equal environment "column") options "")
- (format "%s\\textwidth" column-width)))
+ (format "%s\\columnwidth" column-width)))
;; Block's opening string.
(when (nth 2 env-format)
(concat
@@ -534,15 +557,19 @@ used as a communication channel."
;; overlay specification and the default one is nil.
(let ((action (org-element-property :BEAMER_ACT headline)))
(cond
- ((not action) (list (cons "a" "") (cons "A" "")))
- ((string-match "\\`\\[.*\\]\\'" action)
+ ((not action) (list (cons "a" "") (cons "A" "") (cons "R" "")))
+ ((and (string-prefix-p "[" action)
+ (string-suffix-p "]" action))
(list
(cons "A" (org-beamer--normalize-argument action 'defaction))
- (cons "a" "")))
+ (cons "a" "")
+ (cons "R" action)))
(t
(list (cons "a" (org-beamer--normalize-argument action 'action))
- (cons "A" "")))))
+ (cons "A" "")
+ (cons "R" action)))))
(list (cons "o" options)
+ (cons "O" (or raw-options ""))
(cons "h" title)
(cons "r" raw-title)
(cons "H" (if (equal raw-title "") ""
@@ -578,28 +605,27 @@ as a communication channel."
(when overlay
(org-beamer--normalize-argument
overlay
- (if (string-match "^\\[.*\\]$" overlay) 'defaction
+ (if (string-match "\\`\\[.*\\]\\'" overlay) 'defaction
'action))))
;; Options.
(let ((options (org-element-property :BEAMER_OPT headline)))
(when options
(org-beamer--normalize-argument options 'option)))
;; Resolve reference provided by "BEAMER_ref"
- ;; property. This is done by building a minimal fake
- ;; link and calling the appropriate resolve function,
- ;; depending on the reference syntax.
- (let* ((type
- (progn
- (string-match "^\\(id:\\|#\\|\\*\\)?\\(.*\\)" ref)
- (cond
- ((or (not (match-string 1 ref))
- (equal (match-string 1 ref) "*")) 'fuzzy)
- ((equal (match-string 1 ref) "id:") 'id)
- (t 'custom-id))))
- (link (list 'link (list :path (match-string 2 ref))))
- (target (if (eq type 'fuzzy)
- (org-export-resolve-fuzzy-link link info)
- (org-export-resolve-id-link link info))))
+ ;; property. This is done by building a minimal
+ ;; fake link and calling the appropriate resolve
+ ;; function, depending on the reference syntax.
+ (let ((target
+ (if (string-match "\\`\\(id:\\|#\\)" ref)
+ (org-export-resolve-id-link
+ `(link (:path ,(substring ref (match-end 0))))
+ info)
+ (org-export-resolve-fuzzy-link
+ `(link (:path
+ ;; Look for headlines only.
+ ,(if (eq (string-to-char ref) ?*) ref
+ (concat "*" ref))))
+ info))))
;; Now use user-defined label provided in TARGET
;; headline, or fallback to standard one.
(format "{%s}" (org-beamer--get-label target info)))))))
@@ -640,15 +666,27 @@ as a communication channel."
"Transcode an ITEM element into Beamer code.
CONTENTS holds the contents of the item. INFO is a plist holding
contextual information."
- (let ((action (let ((first-element (car (org-element-contents item))))
- (and (eq (org-element-type first-element) 'paragraph)
- (org-beamer--element-has-overlay-p first-element))))
- (output (org-export-with-backend 'latex item contents info)))
- (if (or (not action) (not (string-match "\\\\item" output))) output
- ;; If the item starts with a paragraph and that paragraph starts
- ;; with an export snippet specifying an overlay, insert it after
- ;; \item command.
- (replace-match (concat "\\\\item" action) nil nil output))))
+ (org-export-with-backend
+ ;; Delegate item export to `latex'. However, we use `beamer'
+ ;; transcoders for objects in the description tag.
+ (org-export-create-backend
+ :parent 'beamer
+ :transcoders
+ (list
+ (cons
+ 'item
+ (lambda (item _c _i)
+ (let ((action
+ (let ((first (car (org-element-contents item))))
+ (and (eq (org-element-type first) 'paragraph)
+ (org-beamer--element-has-overlay-p first))))
+ (output (org-latex-item item contents info)))
+ (if (not (and action (string-match "\\\\item" output))) output
+ ;; If the item starts with a paragraph and that paragraph
+ ;; starts with an export snippet specifying an overlay,
+ ;; append it to the \item command.
+ (replace-match (concat "\\\\item" action) nil nil output)))))))
+ item contents info))
;;;; Keyword
@@ -681,46 +719,16 @@ channel."
"Transcode a LINK object into Beamer code.
CONTENTS is the description part of the link. INFO is a plist
used as a communication channel."
- (let ((type (org-element-property :type link))
- (path (org-element-property :path link)))
- ;; Use \hyperlink command for all internal links.
- (cond
- ((equal type "radio")
- (let ((destination (org-export-resolve-radio-link link info)))
- (if (not destination) contents
- (format "\\hyperlink%s{%s}{%s}"
- (or (org-beamer--element-has-overlay-p link) "")
- (org-export-solidify-link-text
- (org-element-property :value destination))
- contents))))
- ((and (member type '("custom-id" "fuzzy" "id"))
- (let ((destination (if (string= type "fuzzy")
- (org-export-resolve-fuzzy-link link info)
- (org-export-resolve-id-link link info))))
- (case (org-element-type destination)
- (headline
- (let ((label
- (format "sec-%s"
- (mapconcat
- 'number-to-string
- (org-export-get-headline-number
- destination info)
- "-"))))
- (if (and (plist-get info :section-numbers) (not contents))
- (format "\\ref{%s}" label)
- (format "\\hyperlink%s{%s}{%s}"
- (or (org-beamer--element-has-overlay-p link) "")
- label
- contents))))
- (target
- (let ((path (org-export-solidify-link-text path)))
- (if (not contents) (format "\\ref{%s}" path)
- (format "\\hyperlink%s{%s}{%s}"
- (or (org-beamer--element-has-overlay-p link) "")
- path
- contents))))))))
- ;; Otherwise, use `latex' back-end.
- (t (org-export-with-backend 'latex link contents info)))))
+ (or (org-export-custom-protocol-maybe link contents 'beamer)
+ ;; Fall-back to LaTeX export. However, prefer "\hyperlink" over
+ ;; "\hyperref" since the former handles overlay specifications.
+ (let ((latex-link (org-export-with-backend 'latex link contents info)))
+ (if (string-match "\\`\\\\hyperref\\[\\(.*?\\)\\]" latex-link)
+ (replace-match
+ (format "\\\\hyperlink%s{\\1}"
+ (or (org-beamer--element-has-overlay-p link) ""))
+ nil nil latex-link)
+ latex-link))))
;;;; Plain List
@@ -755,7 +763,8 @@ contextual information."
'option)
;; Eventually insert contents and close environment.
contents
- latex-type))))
+ latex-type)
+ info)))
;;;; Radio Target
@@ -766,21 +775,10 @@ TEXT is the text of the target. INFO is a plist holding
contextual information."
(format "\\hypertarget%s{%s}{%s}"
(or (org-beamer--element-has-overlay-p radio-target) "")
- (org-export-solidify-link-text
- (org-element-property :value radio-target))
+ (org-export-get-reference radio-target info)
text))
-;;;; Target
-
-(defun org-beamer-target (target contents info)
- "Transcode a TARGET object into Beamer code.
-CONTENTS is nil. INFO is a plist holding contextual
-information."
- (format "\\hypertarget{%s}{}"
- (org-export-solidify-link-text (org-element-property :value target))))
-
-
;;;; Template
;;
;; Template used is similar to the one used in `latex' back-end,
@@ -790,37 +788,17 @@ information."
"Return complete document string after Beamer conversion.
CONTENTS is the transcoded contents string. INFO is a plist
holding export options."
- (let ((title (org-export-data (plist-get info :title) info)))
+ (let ((title (org-export-data (plist-get info :title) info))
+ (subtitle (org-export-data (plist-get info :subtitle) info)))
(concat
- ;; 1. Time-stamp.
+ ;; Time-stamp.
(and (plist-get info :time-stamp-file)
(format-time-string "%% Created %Y-%m-%d %a %H:%M\n"))
- ;; 2. Document class and packages.
- (let* ((class (plist-get info :latex-class))
- (class-options (plist-get info :latex-class-options))
- (header (nth 1 (assoc class org-latex-classes)))
- (document-class-string
- (and (stringp header)
- (if (not class-options) header
- (replace-regexp-in-string
- "^[ \t]*\\\\documentclass\\(\\(\\[[^]]*\\]\\)?\\)"
- class-options header t nil 1)))))
- (if (not document-class-string)
- (user-error "Unknown LaTeX class `%s'" class)
- (org-latex-guess-babel-language
- (org-latex-guess-inputenc
- (org-element-normalize-string
- (org-splice-latex-header
- document-class-string
- org-latex-default-packages-alist
- org-latex-packages-alist nil
- (concat (org-element-normalize-string
- (plist-get info :latex-header))
- (org-element-normalize-string
- (plist-get info :latex-header-extra))
- (plist-get info :beamer-header-extra)))))
- info)))
- ;; 3. Insert themes.
+ ;; LaTeX compiler
+ (org-latex--insert-compiler info)
+ ;; Document class and packages.
+ (org-latex-make-preamble info)
+ ;; Insert themes.
(let ((format-theme
(function
(lambda (prop command)
@@ -840,11 +818,11 @@ holding export options."
(:beamer-inner-theme "\\useinnertheme")
(:beamer-outer-theme "\\useoutertheme"))
""))
- ;; 4. Possibly limit depth for headline numbering.
+ ;; Possibly limit depth for headline numbering.
(let ((sec-num (plist-get info :section-numbers)))
(when (integerp sec-num)
(format "\\setcounter{secnumdepth}{%d}\n" sec-num)))
- ;; 5. Author.
+ ;; Author.
(let ((author (and (plist-get info :with-author)
(let ((auth (plist-get info :author)))
(and auth (org-export-data auth info)))))
@@ -852,52 +830,52 @@ holding export options."
(org-export-data (plist-get info :email) info))))
(cond ((and author email (not (string= "" email)))
(format "\\author{%s\\thanks{%s}}\n" author email))
- (author (format "\\author{%s}\n" author))
- (t "\\author{}\n")))
- ;; 6. Date.
+ ((or author email) (format "\\author{%s}\n" (or author email)))))
+ ;; Date.
(let ((date (and (plist-get info :with-date) (org-export-get-date info))))
(format "\\date{%s}\n" (org-export-data date info)))
- ;; 7. Title
+ ;; Title
(format "\\title{%s}\n" title)
- ;; 8. Hyperref options.
- (when (plist-get info :latex-hyperref-p)
- (format "\\hypersetup{\n pdfkeywords={%s},\n pdfsubject={%s},\n pdfcreator={%s}}\n"
- (or (plist-get info :keywords) "")
- (or (plist-get info :description) "")
- (if (not (plist-get info :with-creator)) ""
- (plist-get info :creator))))
- ;; 9. Document start.
+ (when (org-string-nw-p subtitle)
+ (concat (format (plist-get info :beamer-subtitle-format) subtitle) "\n"))
+ ;; Beamer-header
+ (let ((beamer-header (plist-get info :beamer-header)))
+ (when beamer-header
+ (format "%s\n" (plist-get info :beamer-header))))
+ ;; 9. Hyperref options.
+ (let ((template (plist-get info :latex-hyperref-template)))
+ (and (stringp template)
+ (format-spec template (org-latex--format-spec info))))
+ ;; Document start.
"\\begin{document}\n\n"
- ;; 10. Title command.
+ ;; Title command.
(org-element-normalize-string
- (cond ((string= "" title) nil)
+ (cond ((not (plist-get info :with-title)) nil)
+ ((string= "" title) nil)
((not (stringp org-latex-title-command)) nil)
((string-match "\\(?:[^%]\\|^\\)%s"
org-latex-title-command)
(format org-latex-title-command title))
(t org-latex-title-command)))
- ;; 11. Table of contents.
+ ;; Table of contents.
(let ((depth (plist-get info :with-toc)))
(when depth
(concat
(format "\\begin{frame}%s{%s}\n"
(org-beamer--normalize-argument
- org-beamer-outline-frame-options 'option)
- org-beamer-outline-frame-title)
+ (plist-get info :beamer-outline-frame-options) 'option)
+ (plist-get info :beamer-outline-frame-title))
(when (wholenump depth)
(format "\\setcounter{tocdepth}{%d}\n" depth))
"\\tableofcontents\n"
"\\end{frame}\n\n")))
- ;; 12. Document's body.
+ ;; Document's body.
contents
- ;; 13. Creator.
- (let ((creator-info (plist-get info :with-creator)))
- (cond
- ((not creator-info) "")
- ((eq creator-info 'comment)
- (format "%% %s\n" (plist-get info :creator)))
- (t (concat (plist-get info :creator) "\n"))))
- ;; 14. Document end.
+ ;; Creator.
+ (if (plist-get info :with-creator)
+ (concat (plist-get info :creator) "\n")
+ "")
+ ;; Document end.
"\\end{document}")))
@@ -933,7 +911,7 @@ value."
(save-excursion
(org-back-to-heading t)
;; Filter out Beamer-related tags and install environment tag.
- (let ((tags (org-remove-if (lambda (x) (string-match "^B_" x))
+ (let ((tags (cl-remove-if (lambda (x) (string-match "^B_" x))
(org-get-tags)))
(env-tag (and (org-string-nw-p value) (concat "B_" value))))
(org-set-tags-to (if env-tag (cons env-tag tags) tags))
@@ -1085,7 +1063,7 @@ aid, but the tag does not have any semantic meaning."
(let* ((envs (append org-beamer-environments-special
org-beamer-environments-extra
org-beamer-environments-default))
- (org-tag-alist
+ (org-current-tag-alist
(append '((:startgroup))
(mapcar (lambda (e) (cons (concat "B_" (car e))
(string-to-char (nth 1 e))))
@@ -1120,30 +1098,6 @@ aid, but the tag does not have any semantic meaning."
(org-entry-put nil "BEAMER_env" (match-string 1 tags)))
(t (org-entry-delete nil "BEAMER_env"))))))
-;;;###autoload
-(defun org-beamer-insert-options-template (&optional kind)
- "Insert a settings template, to make sure users do this right."
- (interactive (progn
- (message "Current [s]ubtree or [g]lobal?")
- (if (eq (read-char-exclusive) ?g) (list 'global)
- (list 'subtree))))
- (if (eq kind 'subtree)
- (progn
- (org-back-to-heading t)
- (org-reveal)
- (org-entry-put nil "EXPORT_LaTeX_CLASS" "beamer")
- (org-entry-put nil "EXPORT_LaTeX_CLASS_OPTIONS" "[presentation]")
- (org-entry-put nil "EXPORT_FILE_NAME" "presentation.pdf")
- (when org-beamer-column-view-format
- (org-entry-put nil "COLUMNS" org-beamer-column-view-format))
- (org-entry-put nil "BEAMER_col_ALL" org-beamer-column-widths))
- (insert "#+LaTeX_CLASS: beamer\n")
- (insert "#+LaTeX_CLASS_OPTIONS: [presentation]\n")
- (when org-beamer-theme (insert "#+BEAMER_THEME: " org-beamer-theme "\n"))
- (when org-beamer-column-view-format
- (insert "#+COLUMNS: " org-beamer-column-view-format "\n"))
- (insert "#+PROPERTY: BEAMER_col_ALL " org-beamer-column-widths "\n")))
-
;;;###autoload
(defun org-beamer-publish-to-latex (plist filename pub-dir)
"Publish an Org file to a Beamer presentation (LaTeX).
@@ -1168,9 +1122,13 @@ Return output file name."
;; working directory and then moved to publishing directory.
(org-publish-attachment
plist
- (org-latex-compile
- (org-publish-org-to
- 'beamer filename ".tex" plist (file-name-directory filename)))
+ ;; Default directory could be anywhere when this function is
+ ;; called. We ensure it is set to source file directory during
+ ;; compilation so as to not break links to external documents.
+ (let ((default-directory (file-name-directory filename)))
+ (org-latex-compile
+ (org-publish-org-to
+ 'beamer filename ".tex" plist (file-name-directory filename))))
pub-dir))
diff --git a/lisp/org/ox-html.el b/lisp/org/ox-html.el
index 86ca3a6bb28..49562fa6918 100644
--- a/lisp/org/ox-html.el
+++ b/lisp/org/ox-html.el
@@ -1,4 +1,4 @@
-;;; ox-html.el --- HTML Back-End for Org Export Engine
+;;; ox-html.el --- HTML Back-End for Org Export Engine -*- lexical-binding: t; -*-
;; Copyright (C) 2011-2017 Free Software Foundation, Inc.
@@ -30,20 +30,24 @@
;;; Dependencies
+(require 'cl-lib)
+(require 'format-spec)
(require 'ox)
(require 'ox-publish)
-(require 'format-spec)
-(eval-when-compile (require 'cl) (require 'table nil 'noerror))
+(require 'table)
;;; Function Declarations
(declare-function org-id-find-id-file "org-id" (id))
(declare-function htmlize-region "ext:htmlize" (beg end))
-(declare-function org-pop-to-buffer-same-window
- "org-compat" (&optional buffer-or-name norecord label))
(declare-function mm-url-decode-entities "mm-url" ())
+(defvar htmlize-css-name-prefix)
+(defvar htmlize-output-type)
+(defvar htmlize-output-type)
+(defvar htmlize-css-name-prefix)
+
;;; Define Back-End
(org-export-define-backend 'html
@@ -72,13 +76,13 @@
(latex-fragment . org-html-latex-fragment)
(line-break . org-html-line-break)
(link . org-html-link)
+ (node-property . org-html-node-property)
(paragraph . org-html-paragraph)
(plain-list . org-html-plain-list)
(plain-text . org-html-plain-text)
(planning . org-html-planning)
(property-drawer . org-html-property-drawer)
(quote-block . org-html-quote-block)
- (quote-section . org-html-quote-section)
(radio-target . org-html-radio-target)
(section . org-html-section)
(special-block . org-html-special-block)
@@ -96,7 +100,6 @@
(underline . org-html-underline)
(verbatim . org-html-verbatim)
(verse-block . org-html-verse-block))
- :export-block "HTML"
:filters-alist '((:filter-options . org-html-infojs-install-script)
(:filter-final-output . org-html-final-function))
:menu-entry
@@ -108,10 +111,10 @@
(if a (org-html-export-to-html t s v b)
(org-open-file (org-html-export-to-html nil s v b)))))))
:options-alist
- '((:html-extension nil nil org-html-extension)
- (:html-link-org-as-html nil nil org-html-link-org-files-as-html)
- (:html-doctype "HTML_DOCTYPE" nil org-html-doctype)
+ '((:html-doctype "HTML_DOCTYPE" nil org-html-doctype)
(:html-container "HTML_CONTAINER" nil org-html-container-element)
+ (:description "DESCRIPTION" nil nil newline)
+ (:keywords "KEYWORDS" nil nil space)
(:html-html5-fancy nil "html5-fancy" org-html-html5-fancy)
(:html-link-use-abs-url nil "html-link-use-abs-url" org-html-link-use-abs-url)
(:html-link-home "HTML_LINK_HOME" nil org-html-link-home)
@@ -121,12 +124,52 @@
(:html-preamble nil "html-preamble" org-html-preamble)
(:html-head "HTML_HEAD" nil org-html-head newline)
(:html-head-extra "HTML_HEAD_EXTRA" nil org-html-head-extra newline)
- (:html-head-include-default-style nil "html-style" org-html-head-include-default-style)
+ (:subtitle "SUBTITLE" nil nil parse)
+ (:html-head-include-default-style
+ nil "html-style" org-html-head-include-default-style)
(:html-head-include-scripts nil "html-scripts" org-html-head-include-scripts)
+ (:html-allow-name-attribute-in-anchors
+ nil nil org-html-allow-name-attribute-in-anchors)
+ (:html-divs nil nil org-html-divs)
+ (:html-checkbox-type nil nil org-html-checkbox-type)
+ (:html-extension nil nil org-html-extension)
+ (:html-footnote-format nil nil org-html-footnote-format)
+ (:html-footnote-separator nil nil org-html-footnote-separator)
+ (:html-footnotes-section nil nil org-html-footnotes-section)
+ (:html-format-drawer-function nil nil org-html-format-drawer-function)
+ (:html-format-headline-function nil nil org-html-format-headline-function)
+ (:html-format-inlinetask-function
+ nil nil org-html-format-inlinetask-function)
+ (:html-home/up-format nil nil org-html-home/up-format)
+ (:html-indent nil nil org-html-indent)
+ (:html-infojs-options nil nil org-html-infojs-options)
+ (:html-infojs-template nil nil org-html-infojs-template)
+ (:html-inline-image-rules nil nil org-html-inline-image-rules)
+ (:html-link-org-files-as-html nil nil org-html-link-org-files-as-html)
+ (:html-mathjax-options nil nil org-html-mathjax-options)
+ (:html-mathjax-template nil nil org-html-mathjax-template)
+ (:html-metadata-timestamp-format nil nil org-html-metadata-timestamp-format)
+ (:html-postamble-format nil nil org-html-postamble-format)
+ (:html-preamble-format nil nil org-html-preamble-format)
+ (:html-table-align-individual-fields
+ nil nil org-html-table-align-individual-fields)
+ (:html-table-caption-above nil nil org-html-table-caption-above)
+ (:html-table-data-tags nil nil org-html-table-data-tags)
+ (:html-table-header-tags nil nil org-html-table-header-tags)
+ (:html-table-use-header-tags-for-first-column
+ nil nil org-html-table-use-header-tags-for-first-column)
+ (:html-tag-class-prefix nil nil org-html-tag-class-prefix)
+ (:html-text-markup-alist nil nil org-html-text-markup-alist)
+ (:html-todo-kwd-class-prefix nil nil org-html-todo-kwd-class-prefix)
+ (:html-toplevel-hlevel nil nil org-html-toplevel-hlevel)
+ (:html-use-infojs nil nil org-html-use-infojs)
+ (:html-validation-link nil nil org-html-validation-link)
+ (:html-viewport nil nil org-html-viewport)
+ (:html-inline-images nil nil org-html-inline-images)
(:html-table-attributes nil nil org-html-table-default-attributes)
- (:html-table-row-tags nil nil org-html-table-row-tags)
+ (:html-table-row-open-tag nil nil org-html-table-row-open-tag)
+ (:html-table-row-close-tag nil nil org-html-table-row-close-tag)
(:html-xml-declaration nil nil org-html-xml-declaration)
- (:html-inline-images nil nil org-html-inline-images)
(:infojs-opt "INFOJS_OPT" nil nil)
;; Redefine regular options.
(:creator "CREATOR" nil org-html-creator-string)
@@ -186,7 +229,7 @@ property on the headline itself.")
@licstart The following is the entire license notice for the
JavaScript code in this tag.
-Copyright (C) 2012-2013 Free Software Foundation, Inc.
+Copyright (C) 2012-2017 Free Software Foundation, Inc.
The JavaScript code in this tag is free software: you can
redistribute it and/or modify it under the terms of the GNU
@@ -232,16 +275,22 @@ for the JavaScript code in this tag.
(defconst org-html-style-default
""
"The default style specification for exported HTML files.
@@ -385,7 +518,7 @@ means to use the maximum value consistent with other options."
* @licstart The following is the entire license notice for the
* JavaScript code in %SCRIPT_PATH.
*
- * Copyright (C) 2012-2013 Free Software Foundation, Inc.
+ * Copyright (C) 2012-2017 Free Software Foundation, Inc.
*
*
* The JavaScript code in this tag is free software: you can
@@ -414,7 +547,7 @@ means to use the maximum value consistent with other options."
@licstart The following is the entire license notice for the
JavaScript code in this tag.
-Copyright (C) 2012-2013 Free Software Foundation, Inc.
+Copyright (C) 2012-2017 Free Software Foundation, Inc.
The JavaScript code in this tag is free software: you can
redistribute it and/or modify it under the terms of the GNU
@@ -447,23 +580,24 @@ Option settings will replace the %MANAGER-OPTIONS cookie."
:package-version '(Org . "8.0")
:type 'string)
-(defun org-html-infojs-install-script (exp-plist backend)
+(defun org-html-infojs-install-script (exp-plist _backend)
"Install script in export options when appropriate.
EXP-PLIST is a plist containing export options. BACKEND is the
export back-end currently used."
(unless (or (memq 'body-only (plist-get exp-plist :export-options))
- (not org-html-use-infojs)
- (and (eq org-html-use-infojs 'when-configured)
- (or (not (plist-get exp-plist :infojs-opt))
- (string= "" (plist-get exp-plist :infojs-opt))
- (string-match "\\"
- (plist-get exp-plist :infojs-opt)))))
- (let* ((template org-html-infojs-template)
+ (not (plist-get exp-plist :html-use-infojs))
+ (and (eq (plist-get exp-plist :html-use-infojs) 'when-configured)
+ (let ((opt (plist-get exp-plist :infojs-opt)))
+ (or (not opt)
+ (string= "" opt)
+ (string-match "\\" opt)))))
+ (let* ((template (plist-get exp-plist :html-infojs-template))
(ptoc (plist-get exp-plist :with-toc))
(hlevels (plist-get exp-plist :headline-levels))
(sdepth hlevels)
(tdepth (if (integerp ptoc) (min ptoc hlevels) hlevels))
(options (plist-get exp-plist :infojs-opt))
+ (infojs-opt (plist-get exp-plist :html-infojs-options))
(table org-html-infojs-opts-table)
style)
(dolist (entry table)
@@ -472,7 +606,7 @@ export back-end currently used."
;; Compute default values for script option OPT from
;; `org-html-infojs-options' variable.
(default
- (let ((default (cdr (assq opt org-html-infojs-options))))
+ (let ((default (cdr (assq opt infojs-opt))))
(if (and (symbolp default) (not (memq default '(t nil))))
(plist-get exp-plist default)
default)))
@@ -483,21 +617,21 @@ export back-end currently used."
options))
(match-string 1 options)
default)))
- (case opt
- (path (setq template
- (replace-regexp-in-string
- "%SCRIPT_PATH" val template t t)))
- (sdepth (when (integerp (read val))
- (setq sdepth (min (read val) sdepth))))
- (tdepth (when (integerp (read val))
- (setq tdepth (min (read val) tdepth))))
- (otherwise (setq val
- (cond
- ((or (eq val t) (equal val "t")) "1")
- ((or (eq val nil) (equal val "nil")) "0")
- ((stringp val) val)
- (t (format "%s" val))))
- (push (cons var val) style)))))
+ (pcase opt
+ (`path (setq template
+ (replace-regexp-in-string
+ "%SCRIPT_PATH" val template t t)))
+ (`sdepth (when (integerp (read val))
+ (setq sdepth (min (read val) sdepth))))
+ (`tdepth (when (integerp (read val))
+ (setq tdepth (min (read val) tdepth))))
+ (_ (setq val
+ (cond
+ ((or (eq val t) (equal val "t")) "1")
+ ((or (eq val nil) (equal val "nil")) "0")
+ ((stringp val) val)
+ (t (format "%s" val))))
+ (push (cons var val) style)))))
;; Now we set the depth of the *generated* TOC to SDEPTH,
;; because the toc will actually determine the splitting. How
;; much of the toc will actually be displayed is governed by the
@@ -509,9 +643,9 @@ export back-end currently used."
(push (cons "TOC_DEPTH" tdepth) style)
;; Build style string.
(setq style (mapconcat
- (lambda (x) (format "org_html_manager.set(\"%s\", \"%s\");"
- (car x)
- (cdr x)))
+ (lambda (x)
+ (format "org_html_manager.set(\"%s\", \"%s\");"
+ (car x) (cdr x)))
style "\n"))
(when (and style (> (length style) 0))
(and (string-match "%MANAGER_OPTIONS" template)
@@ -561,17 +695,9 @@ Warning: non-nil may break indentation of source code blocks."
:package-version '(Org . "8.0")
:type 'boolean)
-(defcustom org-html-use-unicode-chars nil
- "Non-nil means to use unicode characters instead of HTML entities."
- :group 'org-export-html
- :version "24.4"
- :package-version '(Org . "8.0")
- :type 'boolean)
-
;;;; Drawers
-(defcustom org-html-format-drawer-function
- (lambda (name contents) contents)
+(defcustom org-html-format-drawer-function (lambda (_name contents) contents)
"Function called to format a drawer in HTML code.
The function must accept two parameters:
@@ -628,28 +754,30 @@ document title."
:group 'org-export-html
:type 'integer)
-(defcustom org-html-format-headline-function 'ignore
+(defcustom org-html-format-headline-function
+ 'org-html-format-headline-default-function
"Function to format headline text.
-This function will be called with 5 arguments:
+This function will be called with six arguments:
TODO the todo keyword (string or nil).
TODO-TYPE the type of todo (symbol: `todo', `done', nil)
PRIORITY the priority of the headline (integer or nil)
TEXT the main headline text (string).
TAGS the tags (string or nil).
+INFO the export options (plist).
The function result will be used in the section format string."
:group 'org-export-html
- :version "24.4"
- :package-version '(Org . "8.0")
+ :version "26.1"
+ :package-version '(Org . "8.3")
:type 'function)
;;;; HTML-specific
-(defcustom org-html-allow-name-attribute-in-anchors t
+(defcustom org-html-allow-name-attribute-in-anchors nil
"When nil, do not set \"name\" attribute in anchors.
-By default, anchors are formatted with both \"id\" and \"name\"
-attributes, when appropriate."
+By default, when appropriate, anchors are formatted with \"id\"
+but without \"name\" attribute."
:group 'org-export-html
:version "24.4"
:package-version '(Org . "8.0")
@@ -657,21 +785,23 @@ attributes, when appropriate."
;;;; Inlinetasks
-(defcustom org-html-format-inlinetask-function 'ignore
+(defcustom org-html-format-inlinetask-function
+ 'org-html-format-inlinetask-default-function
"Function called to format an inlinetask in HTML code.
-The function must accept six parameters:
+The function must accept seven parameters:
TODO the todo keyword, as a string
TODO-TYPE the todo type, a symbol among `todo', `done' and nil.
PRIORITY the inlinetask priority, as a string
NAME the inlinetask name, as a string.
TAGS the inlinetask tags, as a list of strings.
CONTENTS the contents of the inlinetask, as a string.
+ INFO the export options, as a plist
The function should return the string to be exported."
:group 'org-export-html
- :version "24.4"
- :package-version '(Org . "8.0")
+ :version "26.1"
+ :package-version '(Org . "8.3")
:type 'function)
;;;; LaTeX
@@ -685,24 +815,20 @@ fragments.
This option can also be set with the +OPTIONS line,
e.g. \"tex:mathjax\". Allowed values are:
-nil Ignore math snippets.
-`verbatim' Keep everything in verbatim
-`dvipng' Process the LaTeX fragments to images. This will also
- include processing of non-math environments.
-`imagemagick' Convert the LaTeX fragments to pdf files and use
- imagemagick to convert pdf files to png files.
-`mathjax' Do MathJax preprocessing and arrange for MathJax.js to
- be loaded.
-t Synonym for `mathjax'."
+ nil Ignore math snippets.
+ `verbatim' Keep everything in verbatim
+ `mathjax', t Do MathJax preprocessing and arrange for MathJax.js to
+ be loaded.
+ SYMBOL Any symbol defined in `org-preview-latex-process-alist',
+ e.g., `dvipng'."
:group 'org-export-html
:version "24.4"
:package-version '(Org . "8.0")
:type '(choice
(const :tag "Do not process math in any way" nil)
- (const :tag "Use dvipng to make images" dvipng)
- (const :tag "Use imagemagick to make images" imagemagick)
+ (const :tag "Leave math verbatim" verbatim)
(const :tag "Use MathJax to display math" mathjax)
- (const :tag "Leave math verbatim" verbatim)))
+ (symbol :tag "Convert to image to display math" :value dvipng)))
;;;; Links :: Generic
@@ -710,11 +836,11 @@ t Synonym for `mathjax'."
"Non-nil means make file links to `file.org' point to `file.html'.
When `org-mode' is exporting an `org-mode' file to HTML, links to
non-html files are directly put into a href tag in HTML.
-However, links to other Org-mode files (recognized by the
-extension `.org') should become links to the corresponding html
+However, links to other Org files (recognized by the extension
+\".org\") should become links to the corresponding HTML
file, assuming that the linked `org-mode' file will also be
converted to HTML.
-When nil, the links still point to the plain `.org' file."
+When nil, the links still point to the plain \".org\" file."
:group 'org-export-html
:type 'boolean)
@@ -745,22 +871,20 @@ link's path."
;;;; Plain Text
-(defcustom org-html-protect-char-alist
+(defvar org-html-protect-char-alist
'(("&" . "&")
("<" . "<")
(">" . ">"))
- "Alist of characters to be converted by `org-html-protect'."
- :group 'org-export-html
- :type '(repeat (cons (string :tag "Character")
- (string :tag "HTML equivalent"))))
+ "Alist of characters to be converted by `org-html-encode-plain-text'.")
;;;; Src Block
(defcustom org-html-htmlize-output-type 'inline-css
"Output type to be used by htmlize when formatting code snippets.
-Choices are `css', to export the CSS selectors only, or `inline-css', to
-export the CSS attribute values inline in the HTML. We use as default
-`inline-css', in order to make the resulting HTML self-containing.
+Choices are `css' to export the CSS selectors only,`inline-css'
+to export the CSS attribute values inline in the HTML or `nil' to
+export plain text. We use as default `inline-css', in order to
+make the resulting HTML self-containing.
However, this will fail when using Emacs in batch mode for export, because
then no rich font definitions are in place. It will also not be good if
@@ -771,9 +895,9 @@ a style file to define the look of these classes.
To get a start for your css file, start Emacs session and make sure that
all the faces you are interested in are defined, for example by loading files
in all modes you want. Then, use the command
-\\[org-html-htmlize-generate-css] to extract class definitions."
+`\\[org-html-htmlize-generate-css]' to extract class definitions."
:group 'org-export-html
- :type '(choice (const css) (const inline-css)))
+ :type '(choice (const css) (const inline-css) (const nil)))
(defcustom org-html-htmlize-font-prefix "org-"
"The prefix for CSS class names for htmlize font specifications."
@@ -796,7 +920,7 @@ When exporting to HTML5, these values will be disregarded."
:value-type (string :tag "Value")))
(defcustom org-html-table-header-tags '("
" . "
")
- "The opening tag for table header fields.
+ "The opening and ending tags for table header fields.
This is customizable so that alignment options can be specified.
The first %s will be filled with the scope of the field, either row or col.
The second %s will be replaced by a style entry to align the field.
@@ -806,7 +930,7 @@ See also the variable `org-html-table-align-individual-fields'."
:type '(cons (string :tag "Opening tag") (string :tag "Closing tag")))
(defcustom org-html-table-data-tags '("
" . "
")
- "The opening tag for table data fields.
+ "The opening and ending tags for table data fields.
This is customizable so that alignment options can be specified.
The first %s will be filled with the scope of the field, either row or col.
The second %s will be replaced by a style entry to align the field.
@@ -814,43 +938,50 @@ See also the variable `org-html-table-align-individual-fields'."
:group 'org-export-html
:type '(cons (string :tag "Opening tag") (string :tag "Closing tag")))
-(defcustom org-html-table-row-tags '("
" . "
")
- "The opening and ending tags for table rows.
+(defcustom org-html-table-row-open-tag "
"
+ "The opening tag for table rows.
This is customizable so that alignment options can be specified.
-Instead of strings, these can be Lisp forms that will be
+Instead of strings, these can be a Lisp function that will be
evaluated for each row in order to construct the table row tags.
-During evaluation, these variables will be dynamically bound so that
-you can reuse them:
+The function will be called with these arguments:
- `row-number': row number (0 is the first row)
- `rowgroup-number': group number of current row
- `start-rowgroup-p': non-nil means the row starts a group
- `end-rowgroup-p': non-nil means the row ends a group
- `top-row-p': non-nil means this is the top row
- `bottom-row-p': non-nil means this is the bottom row
+ `number': row number (0 is the first row)
+ `group-number': group number of current row
+ `start-group?': non-nil means the row starts a group
+ `end-group?': non-nil means the row ends a group
+ `top?': non-nil means this is the top row
+ `bottom?': non-nil means this is the bottom row
For example:
-\(setq org-html-table-row-tags
- (cons \\='(cond (top-row-p \"
\")))))
will use the \"tr-top\" and \"tr-bottom\" classes for the top row
and the bottom row, and otherwise alternate between \"tr-odd\" and
\"tr-even\" for odd and even rows."
:group 'org-export-html
- :type '(cons
- (choice :tag "Opening tag"
- (string :tag "Specify")
- (sexp))
- (choice :tag "Closing tag"
- (string :tag "Specify")
- (sexp))))
+ :type '(choice :tag "Opening tag"
+ (string :tag "Specify")
+ (function)))
+
+(defcustom org-html-table-row-close-tag "
"
+ "The closing tag for table rows.
+This is customizable so that alignment options can be specified.
+Instead of strings, this can be a Lisp function that will be
+evaluated for each row in order to construct the table row tags.
+
+See documentation of `org-html-table-row-open-tag'."
+ :group 'org-export-html
+ :type '(choice :tag "Closing tag"
+ (string :tag "Specify")
+ (function)))
(defcustom org-html-table-align-individual-fields t
"Non-nil means attach style attributes for alignment to each table field.
@@ -921,7 +1052,10 @@ publishing, with :html-doctype."
:group 'org-export-html
:version "24.4"
:package-version '(Org . "8.0")
- :type 'string)
+ :type (append
+ '(choice)
+ (mapcar (lambda (x) `(const ,(car x))) org-html-doctype-alist)
+ '((string :tag "Custom doctype" ))))
(defcustom org-html-html5-fancy nil
"Non-nil means using new HTML5 elements.
@@ -954,7 +1088,7 @@ org-info.js for your website."
(content "div" "content")
(postamble "div" "postamble"))
"Alist of the three section elements for HTML export.
-The car of each entry is one of 'preamble, 'content or 'postamble.
+The car of each entry is one of `preamble', `content' or `postamble'.
The cdrs of each entry are the ELEMENT_TYPE and ID for each
section of the exported document.
@@ -973,6 +1107,41 @@ org-info.js for your website."
(list :tag "Postamble" (const :format "" postamble)
(string :tag " id") (string :tag "element"))))
+(defconst org-html-checkbox-types
+ '((unicode .
+ ((on . "☑") (off . "☐") (trans . "☐")))
+ (ascii .
+ ((on . "[X]")
+ (off . "[ ]")
+ (trans . "[-]")))
+ (html .
+ ((on . "")
+ (off . "")
+ (trans . ""))))
+ "Alist of checkbox types.
+The cdr of each entry is an alist list three checkbox types for
+HTML export: `on', `off' and `trans'.
+
+The choices are:
+ `unicode' Unicode characters (HTML entities)
+ `ascii' ASCII characters
+ `html' HTML checkboxes
+
+Note that only the ascii characters implement tri-state
+checkboxes. The other two use the `off' checkbox for `trans'.")
+
+(defcustom org-html-checkbox-type 'ascii
+ "The type of checkboxes to use for HTML export.
+See `org-html-checkbox-types' for for the values used for each
+option."
+ :group 'org-export-html
+ :version "24.4"
+ :package-version '(Org . "8.0")
+ :type '(choice
+ (const :tag "ASCII characters" ascii)
+ (const :tag "Unicode characters" unicode)
+ (const :tag "HTML checkboxes" html)))
+
(defcustom org-html-metadata-timestamp-format "%Y-%m-%d %a %H:%M"
"Format used for timestamps in preamble, postamble and metadata.
See `format-time-string' for more information on its components."
@@ -984,82 +1153,107 @@ See `format-time-string' for more information on its components."
;;;; Template :: Mathjax
(defcustom org-html-mathjax-options
- '((path "http://orgmode.org/mathjax/MathJax.js")
+ '((path "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-AMS_HTML" )
(scale "100")
(align "center")
- (indent "2em")
- (mathml nil))
+ (font "TeX")
+ (linebreaks "false")
+ (autonumber "AMS")
+ (indent "0em")
+ (multlinewidth "85%")
+ (tagindent ".8em")
+ (tagside "right"))
"Options for MathJax setup.
-path The path where to find MathJax
-scale Scaling for the HTML-CSS backend, usually between 100 and 133
-align How to align display math: left, center, or right
-indent If align is not center, how far from the left/right side?
-mathml Should a MathML player be used if available?
- This is faster and reduces bandwidth use, but currently
- sometimes has lower spacing quality. Therefore, the default is
- nil. When browsers get better, this switch can be flipped.
+Alist of the following elements. All values are strings.
+
+path The path to MathJax.
+scale Scaling with HTML-CSS, MathML and SVG output engines.
+align How to align display math: left, center, or right.
+font The font to use with HTML-CSS and SVG output. As of MathJax 2.5
+ the following values are understood: \"TeX\", \"STIX-Web\",
+ \"Asana-Math\", \"Neo-Euler\", \"Gyre-Pagella\",
+ \"Gyre-Termes\", and \"Latin-Modern\".
+linebreaks Let MathJax perform automatic linebreaks. Valid values
+ are \"true\" and \"false\".
+indent If align is not center, how far from the left/right side?
+ Valid values are \"left\" and \"right\"
+multlinewidth The width of the multline environment.
+autonumber How to number equations. Valid values are \"None\",
+ \"all\" and \"AMS Math\".
+tagindent The amount tags are indented.
+tagside Which side to show tags/labels on. Valid values are
+ \"left\" and \"right\"
You can also customize this for each buffer, using something like
-#+MATHJAX: scale:\"133\" align:\"right\" mathml:t path:\"/MathJax/\""
+#+HTML_MATHJAX: align: left indent: 5em tagside: left font: Neo-Euler
+
+For further information about MathJax options, see the MathJax documentation:
+
+ http://docs.mathjax.org/"
:group 'org-export-html
+ :package-version '(Org . "8.3")
:type '(list :greedy t
- (list :tag "path (the path from where to load MathJax.js)"
- (const :format " " path) (string))
- (list :tag "scale (scaling for the displayed math)"
- (const :format " " scale) (string))
- (list :tag "align (alignment of displayed equations)"
- (const :format " " align) (string))
- (list :tag "indent (indentation with left or right alignment)"
- (const :format " " indent) (string))
- (list :tag "mathml (should MathML display be used is possible)"
- (const :format " " mathml) (boolean))))
+ (list :tag "path (the path from where to load MathJax.js)"
+ (const :format " " path) (string))
+ (list :tag "scale (scaling for the displayed math)"
+ (const :format " " scale) (string))
+ (list :tag "align (alignment of displayed equations)"
+ (const :format " " align) (string))
+ (list :tag "font (used to display math)"
+ (const :format " " font)
+ (choice (const "TeX")
+ (const "STIX-Web")
+ (const "Asana-Math")
+ (const "Neo-Euler")
+ (const "Gyre-Pagella")
+ (const "Gyre-Termes")
+ (const "Latin-Modern")))
+ (list :tag "linebreaks (automatic line-breaking)"
+ (const :format " " linebreaks)
+ (choice (const "true")
+ (const "false")))
+ (list :tag "autonumber (when should equations be numbered)"
+ (const :format " " autonumber)
+ (choice (const "AMS")
+ (const "None")
+ (const "All")))
+ (list :tag "indent (indentation with left or right alignment)"
+ (const :format " " indent) (string))
+ (list :tag "multlinewidth (width to use for the multline environment)"
+ (const :format " " multlinewidth) (string))
+ (list :tag "tagindent (the indentation of tags from left or right)"
+ (const :format " " tagindent) (string))
+ (list :tag "tagside (location of tags)"
+ (const :format " " tagside)
+ (choice (const "left")
+ (const "right")))))
(defcustom org-html-mathjax-template
- "
-"
- "The MathJax setup for XHTML files."
+});
+
+"
+ "The MathJax template. See also `org-html-mathjax-options'."
:group 'org-export-html
:type 'string)
@@ -1068,7 +1262,7 @@ You can also customize this for each buffer, using something like
(defcustom org-html-postamble 'auto
"Non-nil means insert a postamble in HTML export.
-When set to 'auto, check against the
+When set to `auto', check against the
`org-export-with-author/email/creator/date' variables to set the
content of the postamble. When set to a string, use this string
as the postamble. When t, insert a string as defined by the
@@ -1101,6 +1295,7 @@ The second element of each list is a format string to format the
postamble itself. This format string can contain these elements:
%t stands for the title.
+ %s stands for the subtitle.
%a stands for the author's name.
%e stands for the author's email.
%d stands for the date.
@@ -1165,6 +1360,7 @@ The second element of each list is a format string to format the
preamble itself. This format string can contain these elements:
%t stands for the title.
+ %s stands for the subtitle.
%a stands for the author's name.
%e stands for the author's email.
%d stands for the date.
@@ -1216,8 +1412,6 @@ ignored."
;;;; Template :: Scripts
-(define-obsolete-variable-alias
- 'org-html-style-include-scripts 'org-html-head-include-scripts "24.4")
(defcustom org-html-head-include-scripts t
"Non-nil means include the JavaScript snippets in exported HTML files.
The actual script is defined in `org-html-scripts' and should
@@ -1229,8 +1423,6 @@ not be modified."
;;;; Template :: Styles
-(define-obsolete-variable-alias
- 'org-html-style-include-default 'org-html-head-include-default-style "24.4")
(defcustom org-html-head-include-default-style t
"Non-nil means include the default style in exported HTML files.
The actual style is defined in `org-html-style-default' and
@@ -1243,7 +1435,6 @@ style information."
;;;###autoload
(put 'org-html-head-include-default-style 'safe-local-variable 'booleanp)
-(define-obsolete-variable-alias 'org-html-style 'org-html-head "24.4")
(defcustom org-html-head ""
"Org-wide head definitions for exported HTML files.
@@ -1293,6 +1484,54 @@ or for publication projects using the :html-head-extra property."
;;;###autoload
(put 'org-html-head-extra 'safe-local-variable 'stringp)
+;;;; Template :: Viewport
+
+(defcustom org-html-viewport '((width "device-width")
+ (initial-scale "1")
+ (minimum-scale "")
+ (maximum-scale "")
+ (user-scalable ""))
+ "Viewport options for mobile-optimized sites.
+
+The following values are recognized
+
+width Size of the viewport.
+initial-scale Zoom level when the page is first loaded.
+minimum-scale Minimum allowed zoom level.
+maximum-scale Maximum allowed zoom level.
+user-scalable Whether zoom can be changed.
+
+The viewport meta tag is inserted if this variable is non-nil.
+
+See the following site for a reference:
+https://developer.mozilla.org/en-US/docs/Mozilla/Mobile/Viewport_meta_tag"
+ :group 'org-export-html
+ :version "26.1"
+ :package-version '(Org . "8.3")
+ :type '(choice (const :tag "Disable" nil)
+ (list :tag "Enable"
+ (list :tag "Width of viewport"
+ (const :format " " width)
+ (choice (const :tag "unset" "")
+ (string)))
+ (list :tag "Initial scale"
+ (const :format " " initial-scale)
+ (choice (const :tag "unset" "")
+ (string)))
+ (list :tag "Minimum scale/zoom"
+ (const :format " " minimum-scale)
+ (choice (const :tag "unset" "")
+ (string)))
+ (list :tag "Maximum scale/zoom"
+ (const :format " " maximum-scale)
+ (choice (const :tag "unset" "")
+ (string)))
+ (list :tag "User scalable/zoomable"
+ (const :format " " user-scalable)
+ (choice (const :tag "unset" "")
+ (const "true")
+ (const "false"))))))
+
;;;; Todos
(defcustom org-html-todo-kwd-class-prefix ""
@@ -1315,22 +1554,33 @@ CSS classes, then this prefix can be very useful."
(let ((dt (downcase (plist-get info :html-doctype))))
(member dt '("html5" "xhtml5" ""))))
+(defun org-html--html5-fancy-p (info)
+ "Non-nil when exporting to HTML5 with fancy elements.
+INFO is the current state of the export process, as a plist."
+ (and (plist-get info :html-html5-fancy)
+ (org-html-html5-p info)))
+
(defun org-html-close-tag (tag attr info)
- (concat "<" tag " " attr
+ "Return close-tag for string TAG.
+ATTR specifies additional attributes. INFO is a property list
+containing current export state."
+ (concat "<" tag
+ (org-string-nw-p (concat " " attr))
(if (org-html-xhtml-p info) " />" ">")))
(defun org-html-doctype (info)
- "Return correct html doctype tag from `org-html-doctype-alist',
-or the literal value of :html-doctype from INFO if :html-doctype
-is not found in the alist.
-INFO is a plist used as a communication channel."
+ "Return correct HTML doctype tag.
+INFO is a plist used as a communication channel. Doctype tag is
+extracted from `org-html-doctype-alist', or the literal value
+of :html-doctype from INFO if :html-doctype is not found in the
+alist."
(let ((dt (plist-get info :html-doctype)))
(or (cdr (assoc dt org-html-doctype-alist)) dt)))
(defun org-html--make-attribute-string (attributes)
"Return a list of attributes, as a string.
-ATTRIBUTES is a plist where values are either strings or nil. An
-attributes with a nil value will be omitted from the result."
+ATTRIBUTES is a plist where values are either strings or nil. An
+attribute with a nil value will be omitted from the result."
(let (output)
(dolist (item attributes (mapconcat 'identity (nreverse output) " "))
(cond ((null item) (pop output))
@@ -1345,15 +1595,13 @@ attributes with a nil value will be omitted from the result."
INFO is a plist used as a communication channel. When optional
arguments CAPTION and LABEL are given, use them for caption and
\"id\" attribute."
- (let ((html5-fancy (and (org-html-html5-p info)
- (plist-get info :html-html5-fancy))))
- (format (if html5-fancy "\n"
- "\n
" contents))
;; Caption.
(if (not (org-string-nw-p caption)) ""
(format (if html5-fancy "\n%s"
@@ -1366,17 +1614,42 @@ SOURCE is a string specifying the location of the image.
ATTRIBUTES is a plist, as returned by
`org-export-read-attribute'. INFO is a plist used as
a communication channel."
- (org-html-close-tag
- "img"
- (org-html--make-attribute-string
- (org-combine-plists
- (list :src source
- :alt (if (string-match-p "^ltxpng/" source)
- (org-html-encode-plain-text
- (org-find-text-property-in-string 'org-latex-src source))
- (file-name-nondirectory source)))
- attributes))
- info))
+ (if (string= "svg" (file-name-extension source))
+ (org-html--svg-image source attributes info)
+ (org-html-close-tag
+ "img"
+ (org-html--make-attribute-string
+ (org-combine-plists
+ (list :src source
+ :alt (if (string-match-p "^ltxpng/" source)
+ (org-html-encode-plain-text
+ (org-find-text-property-in-string 'org-latex-src source))
+ (file-name-nondirectory source)))
+ attributes))
+ info)))
+
+(defun org-html--svg-image (source attributes info)
+ "Return \"object\" embedding svg file SOURCE with given ATTRIBUTES.
+INFO is a plist used as a communication channel.
+
+The special attribute \"fallback\" can be used to specify a
+fallback image file to use if the object embedding is not
+supported. CSS class \"org-svg\" is assigned as the class of the
+object unless a different class is specified with an attribute."
+ (let ((fallback (plist-get attributes :fallback))
+ (attrs (org-html--make-attribute-string
+ (org-combine-plists
+ ;; Remove fallback attribute, which is not meant to
+ ;; appear directly in the attributes string, and
+ ;; provide a default class if none is set.
+ '(:class "org-svg") attributes '(:fallback nil)))))
+ (format ""
+ source
+ attrs
+ (if fallback
+ (org-html-close-tag
+ "img" (format "src=\"%s\" %s" fallback attrs) info)
+ "Sorry, your browser does not support SVG."))))
(defun org-html--textarea-block (element)
"Transcode ELEMENT into a textarea block.
@@ -1388,7 +1661,7 @@ ELEMENT is either a src block or an example block."
(or (plist-get attr :height) (org-count-lines code))
code)))
-(defun org-html--has-caption-p (element &optional info)
+(defun org-html--has-caption-p (element &optional _info)
"Non-nil when ELEMENT has a caption affiliated keyword.
INFO is a plist used as a communication channel. This function
is meant to be used as a predicate for `org-export-get-ordinal' or
@@ -1435,7 +1708,7 @@ produce code that uses these same face definitions."
(when (and (symbolp f) (or (not i) (not (listp i))))
(insert (org-add-props (copy-sequence "1") nil 'face f))))
(htmlize-region (point-min) (point-max))))
- (org-pop-to-buffer-same-window "*html*")
+ (pop-to-buffer-same-window "*html*")
(goto-char (point-min))
(if (re-search-forward ""
+ :syntax-table css-mode-syntax-table
+ :propertize css-syntax-propertize-function
+ :keymap css-mode-map))
+
+(defconst mhtml--js-submode
+ (mhtml--construct-submode 'js-mode
+ :name "JS"
+ :end-tag ""
+ :syntax-table js-mode-syntax-table
+ :propertize #'js-syntax-propertize
+ :keymap js-mode-map))
+
+(defmacro mhtml--with-locals (submode &rest body)
+ (declare (indent 1))
+ `(cl-progv
+ (when ,submode (mapcar #'car (mhtml--submode-captured-locals ,submode)))
+ (when ,submode (mapcar #'cdr (mhtml--submode-captured-locals ,submode)))
+ (cl-progv
+ (when ,submode (mapcar #'car (mhtml--submode-crucial-captured-locals
+ ,submode)))
+ (when ,submode (mapcar #'cdr (mhtml--submode-crucial-captured-locals
+ ,submode)))
+ ,@body)))
+
+(defun mhtml--submode-lighter ()
+ "Mode-line lighter indicating the current submode."
+ (let ((submode (get-text-property (point) 'mhtml-submode)))
+ (if submode
+ (mhtml--submode-name submode)
+ "")))
+
+(defvar font-lock-beg)
+(defvar font-lock-end)
+
+(defun mhtml--extend-font-lock-region ()
+ "Extend the font lock region according to HTML sub-mode needs.
+
+This is used via `font-lock-extend-region-functions'. It ensures
+that the font-lock region is extended to cover either whole
+lines, or to the spot where the submode changes, whichever is
+smallest."
+ (let ((orig-beg font-lock-beg)
+ (orig-end font-lock-end))
+ ;; The logic here may look odd but it is needed to ensure that we
+ ;; do the right thing when trying to limit the search.
+ (save-excursion
+ (goto-char font-lock-beg)
+ ;; previous-single-property-change starts by looking at the
+ ;; previous character, but we're trying to extend a region to
+ ;; include just characters with the same submode as this
+ ;; character.
+ (unless (eobp)
+ (forward-char))
+ (setq font-lock-beg (previous-single-property-change
+ (point) 'mhtml-submode nil
+ (line-beginning-position)))
+ (unless (eq (get-text-property font-lock-beg 'mhtml-submode)
+ (get-text-property orig-beg 'mhtml-submode))
+ (cl-incf font-lock-beg))
+
+ (goto-char font-lock-end)
+ (unless (bobp)
+ (backward-char))
+ (setq font-lock-end (next-single-property-change
+ (point) 'mhtml-submode nil
+ (line-beginning-position 2)))
+ (unless (eq (get-text-property font-lock-end 'mhtml-submode)
+ (get-text-property orig-end 'mhtml-submode))
+ (cl-decf font-lock-end)))
+
+ (or (/= font-lock-beg orig-beg)
+ (/= font-lock-end orig-end))))
+
+(defun mhtml--submode-fontify-one-region (submode beg end &optional loudly)
+ (if submode
+ (mhtml--with-locals submode
+ (save-restriction
+ (font-lock-fontify-region beg end loudly)))
+ (font-lock-set-defaults)
+ (font-lock-default-fontify-region beg end loudly)))
+
+(defun mhtml--submode-fontify-region (beg end loudly)
+ (syntax-propertize end)
+ (let ((orig-beg beg)
+ (orig-end end)
+ (new-beg beg)
+ (new-end end))
+ (while (< beg end)
+ (let ((submode (get-text-property beg 'mhtml-submode))
+ (this-end (next-single-property-change beg 'mhtml-submode
+ nil end)))
+ (let ((extended (mhtml--submode-fontify-one-region submode beg
+ this-end loudly)))
+ ;; If the call extended the region, take note. We track the
+ ;; bounds we were passed and take the union of any extended
+ ;; bounds.
+ (when (and (consp extended)
+ (eq (car extended) 'jit-lock-bounds))
+ (setq new-beg (min new-beg (cadr extended)))
+ ;; Make sure that the next region starts where the
+ ;; extension of this region ends.
+ (setq this-end (cddr extended))
+ (setq new-end (max new-end this-end))))
+ (setq beg this-end)))
+ (when (or (/= orig-beg new-beg)
+ (/= orig-end new-end))
+ (cons 'jit-lock-bounds (cons new-beg new-end)))))
+
+(defvar-local mhtml--last-submode nil
+ "Record the last visited submode, so the cursor-sensor function
+can function properly.")
+
+(defvar-local mhtml--stashed-crucial-variables nil
+ "Alist of stashed values of the crucial variables.")
+
+(defun mhtml--stash-crucial-variables ()
+ (setq mhtml--stashed-crucial-variables
+ (mapcar (lambda (sym)
+ (cons sym (buffer-local-value sym (current-buffer))))
+ mhtml--crucial-variables)))
+
+(defun mhtml--map-in-crucial-variables (alist)
+ (dolist (item alist)
+ (set (car item) (cdr item))))
+
+(defun mhtml--pre-command ()
+ (let ((submode (get-text-property (point) 'mhtml-submode)))
+ (unless (eq submode mhtml--last-submode)
+ ;; If we're entering a submode, and the previous submode was
+ ;; nil, then stash the current values first. This lets the user
+ ;; at least modify some values directly. FIXME maybe always
+ ;; stash into the current mode?
+ (when (and submode (not mhtml--last-submode))
+ (mhtml--stash-crucial-variables))
+ (mhtml--map-in-crucial-variables
+ (if submode
+ (mhtml--submode-crucial-captured-locals submode)
+ mhtml--stashed-crucial-variables))
+ (setq mhtml--last-submode submode))))
+
+(defun mhtml--syntax-propertize-submode (submode end)
+ (save-excursion
+ (when (search-forward (mhtml--submode-end-tag submode) end t)
+ (setq end (match-beginning 0))))
+ (set-text-properties (point) end
+ (list 'mhtml-submode submode
+ 'syntax-table (mhtml--submode-syntax-table submode)
+ ;; We want local-map here so that we act
+ ;; more like the sub-mode and don't
+ ;; override minor mode maps.
+ 'local-map (mhtml--submode-keymap submode)))
+ (funcall (mhtml--submode-propertize submode) (point) end)
+ (goto-char end))
+
+(defun mhtml-syntax-propertize (start end)
+ ;; First remove our special settings from the affected text. They
+ ;; will be re-applied as needed.
+ (remove-list-of-text-properties start end
+ '(syntax-table local-map mhtml-submode))
+ (goto-char start)
+ ;; Be sure to look back one character, because START won't yet have
+ ;; been propertized.
+ (unless (bobp)
+ (let ((submode (get-text-property (1- (point)) 'mhtml-submode)))
+ (if submode
+ ;; Don't search in a comment or string
+ (unless (syntax-ppss-context (syntax-ppss))
+ (mhtml--syntax-propertize-submode submode end))
+ ;; No submode, so do what sgml-mode does.
+ (sgml-syntax-propertize-inside end))))
+ (funcall
+ (syntax-propertize-rules
+ (""
+ (0 (ignore
+ (goto-char (match-end 0))
+ ;; Don't apply in a comment.
+ (unless (syntax-ppss-context (syntax-ppss))
+ (mhtml--syntax-propertize-submode mhtml--css-submode end)))))
+ (""
+ (0 (ignore
+ (goto-char (match-end 0))
+ ;; Don't apply in a comment.
+ (unless (syntax-ppss-context (syntax-ppss))
+ (mhtml--syntax-propertize-submode mhtml--js-submode end)))))
+ sgml-syntax-propertize-rules)
+ ;; Make sure to handle the situation where
+ ;; mhtml--syntax-propertize-submode moved point.
+ (point) end))
+
+(defun mhtml-indent-line ()
+ "Indent the current line as HTML, JS, or CSS, according to its context."
+ (interactive)
+ (let ((submode (save-excursion
+ (back-to-indentation)
+ (get-text-property (point) 'mhtml-submode))))
+ (if submode
+ (save-restriction
+ (let* ((region-start
+ (or (previous-single-property-change (point) 'mhtml-submode)
+ (point)))
+ (base-indent (save-excursion
+ (goto-char region-start)
+ (sgml-calculate-indent))))
+ (cond
+ ((eq mhtml-tag-relative-indent nil)
+ (setq base-indent (- base-indent sgml-basic-offset)))
+ ((eq mhtml-tag-relative-indent 'ignore)
+ (setq base-indent 0)))
+ (narrow-to-region region-start (point-max))
+ (let ((prog-indentation-context (list base-indent
+ (cons (point-min) nil)
+ nil)))
+ (mhtml--with-locals submode
+ ;; indent-line-function was rebound by
+ ;; mhtml--with-locals.
+ (funcall indent-line-function)))))
+ ;; HTML.
+ (sgml-indent-line))))
+
+(defun mhtml--flyspell-check-word ()
+ (let ((submode (get-text-property (point) 'mhtml-submode)))
+ (if submode
+ (flyspell-generic-progmode-verify)
+ t)))
+
+;;;###autoload
+(define-derived-mode mhtml-mode html-mode
+ '((sgml-xml-mode "XHTML+" "HTML+") (:eval (mhtml--submode-lighter)))
+ "Major mode based on `html-mode', but works with embedded JS and CSS.
+
+Code inside a
+ -->
+
+
+
+
+
+
+
+