|
@@ -5085,11 +5085,13 @@ The following commands are available:
|
|
|
'local)
|
|
|
;; Check for running clock before killing a buffer
|
|
|
(org-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)
|
|
|
- ;; Initialize radio targets.
|
|
|
- (org-update-radio-target-regexp)
|
|
|
;; Filling and auto-filling.
|
|
|
(org-setup-filling)
|
|
|
;; Comments.
|
|
@@ -20864,6 +20866,117 @@ hierarchy of headlines by UP levels before marking the subtree."
|
|
|
(call-interactively 'org-mark-element)
|
|
|
(org-mark-element)))
|
|
|
|
|
|
+
|
|
|
+;;; Macros
|
|
|
+
|
|
|
+;; Macros are expanded with `org-macro-replace-all', which relies
|
|
|
+;; internally on `org-macro-expand'.
|
|
|
+
|
|
|
+;; Templates for expansion are stored in the buffer-local variable
|
|
|
+;; `org-macro-templates'. This variable is updated by
|
|
|
+;; `org-macro-initialize-templates'.
|
|
|
+
|
|
|
+
|
|
|
+(defvar 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,
|
|
|
+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)
|
|
|
+
|
|
|
+(defun org-macro-expand (macro)
|
|
|
+ "Return expanded MACRO, as a string.
|
|
|
+MACRO is an object, obtained, for example, with
|
|
|
+`org-element-context'. Return nil if no template was found."
|
|
|
+ (let ((template
|
|
|
+ (cdr (assoc-string (org-element-property :key macro)
|
|
|
+ org-macro-templates
|
|
|
+ ;; Macro names are case-insensitive.
|
|
|
+ t))))
|
|
|
+ (when template
|
|
|
+ (let ((value (replace-regexp-in-string
|
|
|
+ "\\$[0-9]+"
|
|
|
+ (lambda (arg)
|
|
|
+ (or (nth (1- (string-to-number (substring arg 1)))
|
|
|
+ (org-element-property :args macro))
|
|
|
+ ;; No argument provided: remove
|
|
|
+ ;; place-holder.
|
|
|
+ ""))
|
|
|
+ template)))
|
|
|
+ ;; VALUE starts with "(eval": it is a s-exp, `eval' it.
|
|
|
+ (when (string-match "\\`(eval\\>" value)
|
|
|
+ (setq value (eval (read value))))
|
|
|
+ ;; Return string.
|
|
|
+ (format "%s" (or value ""))))))
|
|
|
+
|
|
|
+(defun org-macro-replace-all ()
|
|
|
+ "Replace all macros in current buffer by their expansion."
|
|
|
+ (save-excursion
|
|
|
+ (goto-char (point-min))
|
|
|
+ (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)))
|
|
|
+ (when value
|
|
|
+ (delete-region
|
|
|
+ (org-element-property :begin object)
|
|
|
+ ;; 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)))))))))
|
|
|
+
|
|
|
+(defun org-macro-initialize-templates ()
|
|
|
+ "Collect macro templates defined in current buffer.
|
|
|
+Templates are stored in buffer-local variable
|
|
|
+`org-macro-templates'. In addition to buffer-defined macros, the
|
|
|
+function installs the following ones: \"property\", \"date\",
|
|
|
+\"time\". and, if appropriate, \"input-file\" and
|
|
|
+\"modification-time\"."
|
|
|
+ (org-with-wide-buffer
|
|
|
+ (goto-char (point-min))
|
|
|
+ (let ((case-fold-search t)
|
|
|
+ (set-template
|
|
|
+ (lambda (cell)
|
|
|
+ ;; Add CELL to `org-macro-templates' if there's no
|
|
|
+ ;; association matching its CAR already. Otherwise,
|
|
|
+ ;; replace old association with CELL.
|
|
|
+ (let* ((value (cdr cell))
|
|
|
+ (key (car cell))
|
|
|
+ (old-template (assoc key org-macro-templates)))
|
|
|
+ (if old-template (setcdr old-template value)
|
|
|
+ (push cell org-macro-templates))))))
|
|
|
+ ;; Install buffer-local macros.
|
|
|
+ (while (re-search-forward "^[ \t]*#\\+MACRO:" nil t)
|
|
|
+ (let ((element (org-element-at-point)))
|
|
|
+ (when (eq (org-element-type element) 'keyword)
|
|
|
+ (let ((value (org-element-property :value element)))
|
|
|
+ (when (string-match "^\\(.*?\\)\\(?:\\s-+\\(.*\\)\\)?\\s-*$" value)
|
|
|
+ (funcall set-template
|
|
|
+ (cons (match-string 1 value)
|
|
|
+ (or (match-string 2 value) ""))))))))
|
|
|
+ ;; Install hard-coded macros.
|
|
|
+ (mapc (lambda (cell) (funcall set-template cell))
|
|
|
+ (list
|
|
|
+ (cons "property" "(eval (org-entry-get nil \"$1\" 'selective))")
|
|
|
+ (cons "date" "(eval (format-time-string \"$1\"))")
|
|
|
+ (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 set-template cell))
|
|
|
+ (list
|
|
|
+ (cons "input-file" (file-name-nondirectory visited-file))
|
|
|
+ (cons "modification-time"
|
|
|
+ (format "(eval (format-time-string \"$1\" '%s))"
|
|
|
+ (prin1-to-string
|
|
|
+ (nth 5 (file-attributes visited-file))))))))))))
|
|
|
+
|
|
|
+
|
|
|
;;; Indentation
|
|
|
|
|
|
(defun org-indent-line ()
|