From d2b8bd06a767f37df15e6bbeb8f608c568e50faa Mon Sep 17 00:00:00 2001 From: Nicolas Goaziou Date: Mon, 25 Aug 2014 14:06:21 +0200 Subject: [PATCH 1/6] ox-latex: Small clean-up * lisp/ox-latex.el (org-latex-plain-text): Simplify character escaping. (org-latex-timestamp, org-latex-verse-block, org-latex-compile): Small refactoring. --- lisp/ox-latex.el | 108 ++++++++++++++++++++--------------------------- 1 file changed, 45 insertions(+), 63 deletions(-) diff --git a/lisp/ox-latex.el b/lisp/ox-latex.el index c14d6dcab..4f598f804 100644 --- a/lisp/ox-latex.el +++ b/lisp/ox-latex.el @@ -2045,47 +2045,35 @@ contextual information." "Transcode a TEXT string from Org to LaTeX. TEXT is the string to transcode. INFO is a plist holding contextual information." - (let ((specialp (plist-get info :with-special-strings)) - (output text)) - ;; Protect %, #, &, $, _, { and }. - (while (string-match "\\([^\\]\\|^\\)\\([%$#&{}_]\\)" output) - (setq output - (replace-match - (format "\\%s" (match-string 2 output)) nil t output 2))) - ;; Protect ^. - (setq output - (replace-regexp-in-string - "\\([^\\]\\|^\\)\\(\\^\\)" "\\\\^{}" output nil nil 2)) - ;; Protect \. If special strings are used, be careful not to - ;; protect "\" in "\-" constructs. - (let ((symbols (if specialp "-%$#&{}^_\\" "%$#&{}^_\\"))) - (setq output + (let* ((specialp (plist-get info :with-special-strings)) + (output + ;; Turn LaTeX into \LaTeX{} and TeX into \TeX{}. + (let ((case-fold-search nil)) (replace-regexp-in-string - (format "\\(?:[^\\]\\|^\\)\\(\\\\\\)\\(?:[^%s]\\|$\\)" symbols) - "$\\backslash$" output nil t 1))) - ;; Protect ~. - (setq output - (replace-regexp-in-string - "\\([^\\]\\|^\\)\\(~\\)" "\\textasciitilde{}" output nil t 2)) + "\\<\\(?:La\\)?TeX\\>" "\\\\\\&{}" + ;; Protect ^, ~, %, #, &, $, _, { and }. Also protect \. + ;; However, if special strings are used, be careful not + ;; to protect "\" in "\-" constructs. + (replace-regexp-in-string + (concat "[%$#&{}_~^]\\|\\\\" (and specialp "\\(?:[^-]\\|$\\)")) + (lambda (m) + (case (aref m 0) + (?\\ "$\\\\backslash$") + (?~ "\\\\textasciitilde{}") + (?^ "\\\\^{}") + (t "\\\\\\&"))) + text))))) ;; Activate smart quotes. Be sure to provide original TEXT string ;; since OUTPUT may have been modified. (when (plist-get info :with-smart-quotes) (setq output (org-export-activate-smart-quotes output :latex info text))) - ;; LaTeX into \LaTeX{} and TeX into \TeX{}. - (let ((case-fold-search nil) - (start 0)) - (while (string-match "\\<\\(\\(?:La\\)?TeX\\)\\>" output start) - (setq output (replace-match - (format "\\%s{}" (match-string 1 output)) nil t output) - start (match-end 0)))) ;; Convert special strings. (when specialp - (setq output - (replace-regexp-in-string "\\.\\.\\." "\\ldots{}" output nil t))) + (setq output (replace-regexp-in-string "\\.\\.\\." "\\\\ldots{}" output))) ;; Handle break preservation if required. (when (plist-get info :preserve-breaks) (setq output (replace-regexp-in-string - "\\(\\\\\\\\\\)?[ \t]*\n" " \\\\\\\\\n" output))) + "\\(?:[ \t]*\\\\\\\\\\)?[ \t]*\n" "\\\\\n" output nil t))) ;; Return value. output)) @@ -2874,15 +2862,14 @@ information." "Transcode a TIMESTAMP object from Org to LaTeX. CONTENTS is nil. INFO is a plist holding contextual information." - (let ((value (org-latex-plain-text - (org-timestamp-translate timestamp) info))) - (case (org-element-property :type timestamp) - ((active active-range) - (format (plist-get info :latex-active-timestamp-format) value)) - ((inactive inactive-range) - (format (plist-get info :latex-inactive-timestamp-format) value)) - (otherwise - (format (plist-get info :latex-diary-timestamp-format) value))))) + (let ((value (org-latex-plain-text (org-timestamp-translate timestamp) info))) + (format + (plist-get info + (case (org-element-property :type timestamp) + ((active active-range) :latex-active-timestamp-format) + ((inactive inactive-range) :latex-inactive-timestamp-format) + (otherwise :latex-diary-timestamp-format))) + value))) ;;;; Underline @@ -2916,16 +2903,14 @@ contextual information." ;; character and change each white space at beginning of a line ;; into a space of 1 em. Also change each blank line with ;; a vertical space of 1 em. - (progn - (setq contents (replace-regexp-in-string - "^ *\\\\\\\\$" "\\\\vspace*{1em}" - (replace-regexp-in-string - "\\(\\\\\\\\\\)?[ \t]*\n" " \\\\\\\\\n" contents))) - (while (string-match "^[ \t]+" contents) - (let ((new-str (format "\\hspace*{%dem}" - (length (match-string 0 contents))))) - (setq contents (replace-match new-str nil t contents)))) - (format "\\begin{verse}\n%s\\end{verse}" contents)))) + (format "\\begin{verse}\n%s\\end{verse}" + (replace-regexp-in-string + "^[ \t]+" (lambda (m) (format "\\hspace*{%dem}" (length m))) + (replace-regexp-in-string + "^[ \t]*\\\\\\\\$" "\\vspace*{1em}" + (replace-regexp-in-string + "\\([ \t]*\\\\\\\\\\)?[ \t]*\n" "\\\\\n" + contents nil t) nil t) nil t)))) @@ -3074,17 +3059,15 @@ Return PDF file name or an error if it couldn't be produced." ((consp org-latex-pdf-process) (let ((outbuf (and (not snippet) (get-buffer-create "*Org PDF LaTeX Output*")))) - (mapc - (lambda (command) - (shell-command + (dolist (command org-latex-pdf-process) + (shell-command + (replace-regexp-in-string + "%b" (shell-quote-argument base-name) (replace-regexp-in-string - "%b" (shell-quote-argument base-name) + "%f" (shell-quote-argument full-name) (replace-regexp-in-string - "%f" (shell-quote-argument full-name) - (replace-regexp-in-string - "%o" (shell-quote-argument out-dir) command t t) t t) t t) - outbuf)) - org-latex-pdf-process) + "%o" (shell-quote-argument out-dir) command t t) t t) t t) + outbuf)) ;; Collect standard errors from output buffer. (setq warnings (and (not snippet) (org-latex--collect-warnings outbuf))))) @@ -3126,10 +3109,9 @@ encountered or nil if there was none." (let ((case-fold-search t) (warnings "")) (dolist (warning org-latex-known-warnings) - (save-excursion - (when (save-excursion (re-search-forward (car warning) nil t)) - (setq warnings (concat warnings " " (cdr warning)))))) - (and (org-string-nw-p warnings) (org-trim warnings)))))))) + (when (save-excursion (re-search-forward (car warning) nil t)) + (setq warnings (concat warnings " " (cdr warning))))) + (org-string-nw-p (org-trim warnings)))))))) ;;;###autoload (defun org-latex-publish-to-latex (plist filename pub-dir) From 2f359a650222707dab10270fb77207d9545a0bbf Mon Sep 17 00:00:00 2001 From: Nicolas Goaziou Date: Mon, 25 Aug 2014 14:36:49 +0200 Subject: [PATCH 2/6] ox-latex: Protect special characters in tags * lisp/ox-latex.el (org-latex-format-headline-function, org-latex-format-inlinetask-function): Update docstring. (org-latex-format-headline-default-function, org-latex-format-inlinetask-default-function): Change signature. Protect special characters (e.g., "_"). (org-latex-headline, org-latex-inlinetask): Apply signature change. Thanks to Thorsten Jolitz for reporting it. http://permalink.gmane.org/gmane.emacs.orgmode/90125 --- lisp/ox-latex.el | 55 +++++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/lisp/ox-latex.el b/lisp/ox-latex.el index 4f598f804..64c85eb47 100644 --- a/lisp/ox-latex.el +++ b/lisp/ox-latex.el @@ -400,17 +400,15 @@ Set it to the empty string to ignore the command completely." 'org-latex-format-headline-default-function "Function for formatting the headline's text. -This function will be called with 5 arguments: -TODO the todo keyword (string or nil). +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 as a list of strings (list of strings or nil). +TEXT the main headline text (string) +TAGS the tags (list of strings or nil) +INFO the export options (plist) -The function result will be used in the section format string. - -Use `org-latex-format-headline-default-function' by default, -which format headlines like for Org version prior to 8.0." +The function result will be used in the section format string." :group 'org-export-latex :version "24.4" :package-version '(Org . "8.0") @@ -683,17 +681,16 @@ The default function simply returns the value of CONTENTS." 'org-latex-format-inlinetask-default-function "Function called to format an inlinetask in LaTeX code. -The function must accept six 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. +The function must accept seven parameters: + TODO the todo keyword (string or nil) + TODO-TYPE the todo type (symbol: `todo', `done', nil) + PRIORITY the inlinetask priority (integer or nil) + NAME the inlinetask name (string) + TAGS the inlinetask tags (list of strings or nil) + CONTENTS the contents of the inlinetask (string or nil) + INFO the export options (plist) -The function should return the string to be exported. - -Use `org-latex-format-headline-default-function' by default." +The function should return the string to be exported." :group 'org-export-latex :type 'function :version "24.5" @@ -1466,7 +1463,7 @@ holding contextual information." ;; Create the headline text along with a no-tag version. ;; The latter is required to remove tags from toc. (full-text (funcall (plist-get info :latex-format-headline-function) - todo todo-type priority text tags)) + todo todo-type priority text tags info)) ;; Associate \label to the headline for internal links. (headline-label (let ((custom-label @@ -1514,7 +1511,8 @@ holding contextual information." (org-export-data-with-backend (org-export-get-alt-title headline info) section-back-end info) - (and (eq (plist-get info :with-tags) t) tags)))) + (and (eq (plist-get info :with-tags) t) tags) + info))) (if (and numberedp opt-title (not (equal opt-title full-text)) (string-match "\\`\\\\\\(.*?[^*]\\){" section-fmt)) @@ -1532,7 +1530,7 @@ holding contextual information." (concat headline-label pre-blanks contents)))))))) (defun org-latex-format-headline-default-function - (todo todo-type priority text tags) + (todo todo-type priority text tags info) "Default format function for a headline. See `org-latex-format-headline-function' for details." (concat @@ -1540,7 +1538,9 @@ See `org-latex-format-headline-function' for details." (and priority (format "\\framebox{\\#%c} " priority)) text (and tags - (format "\\hfill{}\\textsc{%s}" (mapconcat 'identity tags ":"))))) + (format "\\hfill{}\\textsc{%s}" + (mapconcat (lambda (tag) (org-latex-plain-text tag info)) + tags ":"))))) ;;;; Horizontal Rule @@ -1621,18 +1621,21 @@ holding contextual information." (and label (format "\\label{%s}\n" label))) contents))) (funcall (plist-get info :latex-format-inlinetask-function) - todo todo-type priority title tags contents))) + todo todo-type priority title tags contents info))) (defun org-latex-format-inlinetask-default-function - (todo todo-type priority title tags contents) + (todo todo-type priority title tags contents info) "Default format function for a inlinetasks. See `org-latex-format-inlinetask-function' for details." (let ((full-title (concat (when todo (format "\\textbf{\\textsf{\\textsc{%s}}} " todo)) (when priority (format "\\framebox{\\#%c} " priority)) title - (when tags (format "\\hfill{}\\textsc{:%s:}" - (mapconcat #'identity tags ":")))))) + (when tags + (format "\\hfill{}\\textsc{:%s:}" + (mapconcat + (lambda (tag) (org-latex-plain-text tag info)) + tags ":")))))) (concat "\\begin{center}\n" "\\fbox{\n" "\\begin{minipage}[c]{.6\\textwidth}\n" From 93694baf628841446d9eff878441b89a7706a3e6 Mon Sep 17 00:00:00 2001 From: Nicolas Goaziou Date: Mon, 25 Aug 2014 14:44:07 +0200 Subject: [PATCH 3/6] ORG-NEWS: Report signature changes --- etc/ORG-NEWS | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index 048f87fbc..78cdf6602 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -50,12 +50,13 @@ defining export blocks. Note that If BACKEND is a derived back-end and doesn't implement its own special block translator already, there is nothing to change. The parent back-end will take care of such blocks. -*** ~org-html-format-headline-function~ requires an additional argument -The function provided is required to accept export options, as -a plist, as its final (sixth) argument. -*** ~org-html-format-inlinetask-function~ requires an additional argument -The function provided is required to accept export options, as -a plist, as its final (seventh) argument. +*** Signature changes +The following functions require an additional argument. See their +docstring for more information. +- ~org-html-format-headline-function~ +- ~org-html-format-inlinetask-function~ +- ~org-latex-format-headline-function~ +- ~org-latex-format-inlinetask-function~ ** Removed functions *** Removed function ~org-beamer-insert-options-template~ This function inserted a Beamer specific template at point or in From e191a76ddd63845692b46fbc41f2bdb9eb688e5a Mon Sep 17 00:00:00 2001 From: Nicolas Goaziou Date: Mon, 25 Aug 2014 15:19:24 +0200 Subject: [PATCH 4/6] org-agenda: Fix order when writing to an ".org" file * lisp/org-agenda.el (org-agenda-write): Write headings in proper order. `org-paste-subtree' leaves point before inserted text, so there is no need to reverse contents. http://permalink.gmane.org/gmane.emacs.orgmode/89867 --- lisp/org-agenda.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/org-agenda.el b/lisp/org-agenda.el index 0067165e6..4b6385b92 100644 --- a/lisp/org-agenda.el +++ b/lisp/org-agenda.el @@ -3352,7 +3352,7 @@ If AGENDA-BUFFER-NAME, use this as the buffer name for the agenda to write." content))) (find-file file) (erase-buffer) - (mapcar (lambda (s) (org-paste-subtree 1 s)) (reverse content)) + (dolist (s content) (org-paste-subtree 1 s)) (write-file file) (kill-buffer (current-buffer)) (message "Org file written to %s" file))) From 409913b253de2de1b49f9468a4b77b9292e090c4 Mon Sep 17 00:00:00 2001 From: Nicolas Goaziou Date: Mon, 25 Aug 2014 15:32:35 +0200 Subject: [PATCH 5/6] Fix `org-promote' error * lisp/org.el (org-called-with-limited-levels): Initialize variable. http://permalink.gmane.org/gmane.emacs.orgmode/90119 --- lisp/org.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lisp/org.el b/lisp/org.el index 9c1867759..60658f439 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -6720,7 +6720,8 @@ in special contexts. (setq org-cycle-global-status 'overview) (run-hook-with-args 'org-cycle-hook 'overview))))) -(defvar org-called-with-limited-levels);Dyn-bound in ̀org-with-limited-levels'. +(defvar org-called-with-limited-levels nil + "Non-nil when `org-with-limited-levels' is currently active.") (defun org-cycle-internal-local () "Do the local cycling action." From 67ae102b4be87976240555d1c0d80ee55906f53c Mon Sep 17 00:00:00 2001 From: Nicolas Goaziou Date: Tue, 26 Aug 2014 00:08:38 +0200 Subject: [PATCH 6/6] ox-icalendar: Speed up `org-agenda-write' process * lisp/ox-icalendar.el (org-icalendar-create-uid): Remove unused optional argument. (org-icalendar--combine-files): Change signature. Simplify process. (org-icalendar-combine-agenda-files): Apply signature change. Do not check anymore ICALENDAR-MARK property. (org-icalendar-entry): Do not check anymore ICALENDAR-MARK property. (org-icalendar-export-to-ics): Comply to comments. (org-icalendar-export-current-agenda): Rewrite function. * lisp/org-agenda.el (org-agenda-write): Update docstring. Instead of parsing every agenda before picking up needed entries, copy these entries in a temporary buffer, then export it. --- lisp/org-agenda.el | 23 ++-- lisp/ox-icalendar.el | 302 +++++++++++++++++++------------------------ 2 files changed, 147 insertions(+), 178 deletions(-) diff --git a/lisp/org-agenda.el b/lisp/org-agenda.el index 7015ad686..bcbacf098 100644 --- a/lisp/org-agenda.el +++ b/lisp/org-agenda.el @@ -3312,19 +3312,20 @@ This ensures the export commands can easily use it." (defvar org-agenda-write-buffer-name "Agenda View") (defun org-agenda-write (file &optional open nosettings agenda-bufname) "Write the current buffer (an agenda view) as a file. + Depending on the extension of the file name, plain text (.txt), HTML (.html or .htm), PDF (.pdf) or Postscript (.ps) is produced. -If the extension is .ics, run icalendar export over all files used -to construct the agenda and limit the export to entries listed in the -agenda now. -If the extension is .org, collect all subtrees corresponding to the -agenda entries and add them in an .org file. -With prefix argument OPEN, open the new file immediately. -If NOSETTINGS is given, do not scope the settings of -`org-agenda-exporter-settings' into the export commands. This is used when -the settings have already been scoped and we do not wish to overrule other, -higher priority settings. -If AGENDA-BUFFER-NAME, use this as the buffer name for the agenda to write." +If the extension is .ics, translate visible agenda into iCalendar +format. If the extension is .org, collect all subtrees +corresponding to the agenda entries and add them in an .org file. + +With prefix argument OPEN, open the new file immediately. If +NOSETTINGS is given, do not scope the settings of +`org-agenda-exporter-settings' into the export commands. This is +used when the settings have already been scoped and we do not +wish to overrule other, higher priority settings. If +AGENDA-BUFFER-NAME is provided, use this as the buffer name for +the agenda to write." (interactive "FWrite agenda to file: \nP") (if (or (not (file-writable-p file)) (and (file-exists-p file) diff --git a/lisp/ox-icalendar.el b/lisp/ox-icalendar.el index 8d62ae2f7..7ebee3582 100644 --- a/lisp/ox-icalendar.el +++ b/lisp/ox-icalendar.el @@ -269,11 +269,7 @@ re-read the iCalendar file.") (:icalendar-store-UID nil nil org-icalendar-store-UID) (:icalendar-timezone nil nil org-icalendar-timezone) (:icalendar-use-deadline nil nil org-icalendar-use-deadline) - (:icalendar-use-scheduled nil nil org-icalendar-use-scheduled) - ;; The following property will be non-nil when export has been - ;; started from org-agenda-mode. In this case, any entry without - ;; a non-nil "ICALENDAR_MARK" property will be ignored. - (:icalendar-agenda-view nil nil nil)) + (:icalendar-use-scheduled nil nil org-icalendar-use-scheduled)) :filters-alist '((:filter-headline . org-icalendar-clear-blank-lines)) :menu-entry @@ -288,22 +284,18 @@ re-read the iCalendar file.") ;;; Internal Functions -(defun org-icalendar-create-uid (file &optional bell h-markers) +(defun org-icalendar-create-uid (file &optional bell) "Set ID property on headlines missing it in FILE. When optional argument BELL is non-nil, inform the user with -a message if the file was modified. With optional argument -H-MARKERS non-nil, it is a list of markers for the headlines -which will be updated." - (let ((pt (if h-markers (goto-char (car h-markers)) (point-min))) - modified-flag) +a message if the file was modified." + (let (modified-flag) (org-map-entries (lambda () (let ((entry (org-element-at-point))) - (unless (or (< (point) pt) (org-element-property :ID entry)) + (unless (org-element-property :ID entry) (org-id-get-create) (setq modified-flag t) - (forward-line)) - (when h-markers (setq org-map-continue-from (pop h-markers))))) + (forward-line)))) nil nil 'comment) (when (and bell modified-flag) (message "ID properties created in file \"%s\"" file) @@ -534,99 +526,97 @@ inlinetask within the section." (cons 'org-data (cons nil (org-element-contents first)))))))) (concat - (unless (and (plist-get info :icalendar-agenda-view) - (not (org-element-property :ICALENDAR-MARK entry))) - (let ((todo-type (org-element-property :todo-type entry)) - (uid (or (org-element-property :ID entry) (org-id-new))) - (summary (org-icalendar-cleanup-string - (or (org-element-property :SUMMARY entry) - (org-export-data - (org-element-property :title entry) info)))) - (loc (org-icalendar-cleanup-string - (org-element-property :LOCATION entry))) - ;; Build description of the entry from associated - ;; section (headline) or contents (inlinetask). - (desc - (org-icalendar-cleanup-string - (or (org-element-property :DESCRIPTION entry) - (let ((contents (org-export-data inside info))) - (cond - ((not (org-string-nw-p contents)) nil) - ((wholenump org-icalendar-include-body) - (let ((contents (org-trim contents))) - (substring - contents 0 (min (length contents) - org-icalendar-include-body)))) - (org-icalendar-include-body (org-trim contents))))))) - (cat (org-icalendar-get-categories entry info))) - (concat - ;; Events: Delegate to `org-icalendar--vevent' to - ;; generate "VEVENT" component from scheduled, deadline, - ;; or any timestamp in the entry. - (let ((deadline (org-element-property :deadline entry))) - (and deadline - (memq (if todo-type 'event-if-todo 'event-if-not-todo) - org-icalendar-use-deadline) - (org-icalendar--vevent - entry deadline (concat "DL-" uid) - (concat "DL: " summary) loc desc cat))) - (let ((scheduled (org-element-property :scheduled entry))) - (and scheduled - (memq (if todo-type 'event-if-todo 'event-if-not-todo) - org-icalendar-use-scheduled) - (org-icalendar--vevent - entry scheduled (concat "SC-" uid) - (concat "S: " summary) loc desc cat))) - ;; When collecting plain timestamps from a headline and - ;; its title, skip inlinetasks since collection will - ;; happen once ENTRY is one of them. + (let ((todo-type (org-element-property :todo-type entry)) + (uid (or (org-element-property :ID entry) (org-id-new))) + (summary (org-icalendar-cleanup-string + (or (org-element-property :SUMMARY entry) + (org-export-data + (org-element-property :title entry) info)))) + (loc (org-icalendar-cleanup-string + (org-element-property :LOCATION entry))) + ;; Build description of the entry from associated section + ;; (headline) or contents (inlinetask). + (desc + (org-icalendar-cleanup-string + (or (org-element-property :DESCRIPTION entry) + (let ((contents (org-export-data inside info))) + (cond + ((not (org-string-nw-p contents)) nil) + ((wholenump org-icalendar-include-body) + (let ((contents (org-trim contents))) + (substring + contents 0 (min (length contents) + org-icalendar-include-body)))) + (org-icalendar-include-body (org-trim contents))))))) + (cat (org-icalendar-get-categories entry info))) + (concat + ;; Events: Delegate to `org-icalendar--vevent' to generate + ;; "VEVENT" component from scheduled, deadline, or any + ;; timestamp in the entry. + (let ((deadline (org-element-property :deadline entry))) + (and deadline + (memq (if todo-type 'event-if-todo 'event-if-not-todo) + org-icalendar-use-deadline) + (org-icalendar--vevent + entry deadline (concat "DL-" uid) + (concat "DL: " summary) loc desc cat))) + (let ((scheduled (org-element-property :scheduled entry))) + (and scheduled + (memq (if todo-type 'event-if-todo 'event-if-not-todo) + org-icalendar-use-scheduled) + (org-icalendar--vevent + entry scheduled (concat "SC-" uid) + (concat "S: " summary) loc desc cat))) + ;; When collecting plain timestamps from a headline and its + ;; title, skip inlinetasks since collection will happen once + ;; ENTRY is one of them. + (let ((counter 0)) + (mapconcat + #'identity + (org-element-map (cons (org-element-property :title entry) + (org-element-contents inside)) + 'timestamp + (lambda (ts) + (when (let ((type (org-element-property :type ts))) + (case (plist-get info :with-timestamps) + (active (memq type '(active active-range))) + (inactive (memq type '(inactive inactive-range))) + ((t) t))) + (let ((uid (format "TS%d-%s" (incf counter) uid))) + (org-icalendar--vevent + entry ts uid summary loc desc cat)))) + info nil (and (eq type 'headline) 'inlinetask)) + "")) + ;; Task: First check if it is appropriate to export it. If + ;; so, call `org-icalendar--vtodo' to transcode it into + ;; a "VTODO" component. + (when (and todo-type + (case (plist-get info :icalendar-include-todo) + (all t) + (unblocked + (and (eq type 'headline) + (not (org-icalendar-blocked-headline-p + entry info)))) + ((t) (eq todo-type 'todo)))) + (org-icalendar--vtodo entry uid summary loc desc cat)) + ;; Diary-sexp: Collect every diary-sexp element within ENTRY + ;; and its title, and transcode them. If ENTRY is + ;; a headline, skip inlinetasks: they will be handled + ;; separately. + (when org-icalendar-include-sexps (let ((counter 0)) - (mapconcat - #'identity - (org-element-map (cons (org-element-property :title entry) - (org-element-contents inside)) - 'timestamp - (lambda (ts) - (when (let ((type (org-element-property :type ts))) - (case (plist-get info :with-timestamps) - (active (memq type '(active active-range))) - (inactive (memq type '(inactive inactive-range))) - ((t) t))) - (let ((uid (format "TS%d-%s" (incf counter) uid))) - (org-icalendar--vevent - entry ts uid summary loc desc cat)))) - info nil (and (eq type 'headline) 'inlinetask)) - "")) - ;; Task: First check if it is appropriate to export it. - ;; If so, call `org-icalendar--vtodo' to transcode it - ;; into a "VTODO" component. - (when (and todo-type - (case (plist-get info :icalendar-include-todo) - (all t) - (unblocked - (and (eq type 'headline) - (not (org-icalendar-blocked-headline-p - entry info)))) - ((t) (eq todo-type 'todo)))) - (org-icalendar--vtodo entry uid summary loc desc cat)) - ;; Diary-sexp: Collect every diary-sexp element within - ;; ENTRY and its title, and transcode them. If ENTRY is - ;; a headline, skip inlinetasks: they will be handled - ;; separately. - (when org-icalendar-include-sexps - (let ((counter 0)) - (mapconcat #'identity - (org-element-map - (cons (org-element-property :title entry) - (org-element-contents inside)) - 'diary-sexp - (lambda (sexp) - (org-icalendar-transcode-diary-sexp - (org-element-property :value sexp) - (format "DS%d-%s" (incf counter) uid) - summary)) - info nil (and (eq type 'headline) 'inlinetask)) - "")))))) + (mapconcat #'identity + (org-element-map + (cons (org-element-property :title entry) + (org-element-contents inside)) + 'diary-sexp + (lambda (sexp) + (org-icalendar-transcode-diary-sexp + (org-element-property :value sexp) + (format "DS%d-%s" (incf counter) uid) + summary)) + info nil (and (eq type 'headline) 'inlinetask)) + ""))))) ;; If ENTRY is a headline, call current function on every ;; inlinetask within it. In agenda export, this is independent ;; from the mark (or lack thereof) on the entry. @@ -833,7 +823,8 @@ Return ICS file name." ;; links will not be collected at the end of sections. (let ((outfile (org-export-output-file-name ".ics" subtreep))) (org-export-to-file 'icalendar outfile - async subtreep visible-only body-only '(:ascii-charset utf-8) + async subtreep visible-only body-only + '(:ascii-charset utf-8 :ascii-links-to-notes nil) (lambda (file) (run-hook-with-args 'org-icalendar-after-save-hook file) nil)))) @@ -888,50 +879,44 @@ The file is stored under the name chosen in (org-export-add-to-stack (expand-file-name org-icalendar-combined-agenda-file) 'icalendar)) - `(apply 'org-icalendar--combine-files nil ',files))) - (apply 'org-icalendar--combine-files nil (org-agenda-files t)))) + `(apply 'org-icalendar--combine-files ',files))) + (apply 'org-icalendar--combine-files (org-agenda-files t)))) (defun org-icalendar-export-current-agenda (file) "Export current agenda view to an iCalendar FILE. This function assumes major mode for current buffer is `org-agenda-mode'." - (let (org-export-babel-evaluate ; Don't evaluate Babel block - (org-icalendar-combined-agenda-file file) - (marker-list - ;; Collect the markers pointing to entries in the current - ;; agenda buffer. - (let (markers) - (save-excursion - (goto-char (point-min)) - (while (not (eobp)) - (let ((m (or (org-get-at-bol 'org-hd-marker) - (org-get-at-bol 'org-marker)))) - (and m (push m markers))) - (beginning-of-line 2))) - (nreverse markers)))) - (apply 'org-icalendar--combine-files - ;; Build restriction alist. - (let (restriction) - ;; Sort markers in each association within RESTRICTION. - (mapcar (lambda (x) (setcdr x (sort (copy-sequence (cdr x)) '<)) x) - (dolist (m marker-list restriction) - (let* ((pos (marker-position m)) - (file (buffer-file-name - (org-base-buffer (marker-buffer m)))) - (file-markers (assoc file restriction))) - ;; Add POS in FILE association if one exists - ;; or create a new association for FILE. - (if file-markers (push pos (cdr file-markers)) - (push (list file pos) restriction)))))) - (org-agenda-files nil 'ifmode)))) + (let* ((org-export-babel-evaluate) ; Don't evaluate Babel block. + (contents + (org-export-string-as + (with-output-to-string + (save-excursion + (let ((p (point-min))) + (while (setq p (next-single-property-change p 'org-hd-marker)) + (let ((m (get-text-property p 'org-hd-marker))) + (when m + (with-current-buffer (marker-buffer m) + (org-with-wide-buffer + (goto-char (marker-position m)) + (princ + (org-element-normalize-string + (buffer-substring + (point) (progn (outline-next-heading) (point))))))))) + (forward-line))))) + 'icalendar file))) + (with-temp-file file + (insert + (org-icalendar--vcalendar + org-icalendar-combined-name + user-full-name + org-icalendar-combined-description + (or (org-string-nw-p org-icalendar-timezone) (cadr (current-time-zone))) + contents))) + (run-hook-with-args 'org-icalendar-after-save-hook file))) -(defun org-icalendar--combine-files (restriction &rest files) +(defun org-icalendar--combine-files (&rest files) "Combine entries from multiple files into an iCalendar file. -RESTRICTION, when non-nil, is an alist where key is a file name -and value a list of buffer positions pointing to entries that -should appear in the calendar. It only makes sense if the -function was called from an agenda buffer. FILES is a list of -files to build the calendar from." +FILES is a list of files to build the calendar from." (org-agenda-prepare-buffers files) (unwind-protect (progn @@ -955,29 +940,12 @@ files to build the calendar from." (catch 'nextfile (org-check-agenda-file file) (with-current-buffer (org-get-agenda-file-buffer file) - (let ((marks (cdr (assoc (expand-file-name file) - restriction)))) - ;; Create ID if necessary. - (when org-icalendar-store-UID - (org-icalendar-create-uid file t marks)) - (unless (and restriction (not marks)) - ;; Add a hook adding :ICALENDAR_MARK: property - ;; to each entry appearing in agenda view. - ;; Use `apply-partially' because the function - ;; still has to accept one argument. - (let ((org-export-before-processing-hook - (cons (apply-partially - (lambda (m-list dummy) - (mapc (lambda (m) - (org-entry-put - m "ICALENDAR-MARK" "t")) - m-list)) - (sort marks '>)) - org-export-before-processing-hook))) - (org-export-as - 'icalendar nil nil t - (list :ascii-charset 'utf-8 - :icalendar-agenda-view restriction)))))))) + ;; Create ID if necessary. + (when org-icalendar-store-UID + (org-icalendar-create-uid file t)) + (org-export-as + 'icalendar nil nil t + '(:ascii-charset utf-8 :ascii-links-to-notes nil))))) files "") ;; BBDB anniversaries. (when (and org-icalendar-include-bbdb-anniversaries