summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicolas Goaziou <n.goaziou@gmail.com>2012-12-02 09:43:42 +0100
committerNicolas Goaziou <n.goaziou@gmail.com>2012-12-13 15:36:51 +0100
commitffb630b85dc53d494e1467235f166861b3f56124 (patch)
tree99305bcf8dbe195b9e4fb3edb7a3a5ffccf81256
parent5c1eab535bca9b15da76c2644d5d7f7bfc5fbcd3 (diff)
downloadorg-mode-ffb630b85dc53d494e1467235f166861b3f56124.tar.gz
org-export: Add asynchronous process wrapper for export
* contrib/lisp/org-export.el (org-export-async-stack, org-export-async-debug, org-export-in-background, org-export-async-init-file, org-export-stack-mode-map): New variables. (org-export-async-start): New macro. (org-export--stack-source-at-point, org-export--stack-refresh, org-export-add-to-stack, org-export--stack-remove, org-export--stack-view, org-export--stack-clear, org-export-stack, org-export-copy-buffer, org-export--generate-copy-script): New functions. (org-export-dispatch, org-export-dispatch-ui): Allow to toggle asynchronous export. (org-export-with-buffer-copy): Renamed from `org-export-with-current-buffer-copy'. (org-export-execute-babel-code): Use new function to copy a buffer. (org-export-as): Remove all text properties from output so it still can be sent to the original process.
-rw-r--r--contrib/lisp/org-export.el511
1 files changed, 440 insertions, 71 deletions
diff --git a/contrib/lisp/org-export.el b/contrib/lisp/org-export.el
index db0acb0..acf84ff 100644
--- a/contrib/lisp/org-export.el
+++ b/contrib/lisp/org-export.el
@@ -65,8 +65,14 @@
;; customizable should belong to the `org-export-BACKEND' group.
;;
;; Tools for common tasks across back-ends are implemented in the
-;; penultimate part of this file. A dispatcher for standard back-ends
-;; is provided in the last one.
+;; following part of then file.
+;;
+;; Then, a wrapper macro for asynchronous export,
+;; `org-export-async-start', along with tools to display results. are
+;; given in the penultimate part.
+;;
+;; Eventually, a dispatcher (`org-export-dispatch') for standard
+;; back-ends is provided in the last one.
;;; Code:
@@ -251,6 +257,25 @@ whose extension is either \"png\", \"jpeg\", \"jpg\", \"gif\",
See `org-export-inline-image-p' for more information about
rules.")
+(defvar org-export-async-debug nil
+ "Non-nil means asynchronous export process should leave data behind.
+
+This data is found in the appropriate \"*Org Export Process*\"
+buffer, and in files prefixed with \"org-export-process\" and
+located in `temporary-file-directory'.
+
+When non-nil, it will also set `debug-on-error' to a non-nil
+value in the external process.")
+
+(defvar org-export-stack-contents nil
+ "Record asynchronously generated export results and processes.
+This is an alist: its CAR is the source of the
+result (destination file or buffer for a finished process,
+original buffer for a running one) and its CDR is a list
+containing the back-end used, as a symbol, and either a process
+or the time at which it finished. It is used to build the menu
+from `org-export-stack'.")
+
(defvar org-export-registered-backends nil
"List of backends currently available in the exporter.
@@ -703,6 +728,21 @@ these cases."
:group 'org-export-general
:type 'boolean)
+(defcustom org-export-in-background nil
+ "Non-nil means export and publishing commands will run in background.
+Results from an asynchronous export are never displayed. You can
+retrieve them with \\[org-export-stack]."
+ :group 'org-export-general
+ :type 'boolean)
+
+(defcustom org-export-async-init-file user-init-file
+ "File used to initialize external export process.
+Value must be an absolute file name. It defaults to user's
+initialization file. Though, a specific configuration makes the
+process faster and the export more portable."
+ :group 'org-export-general
+ :type '(file :must-match t))
+
(defcustom org-export-dispatch-use-expert-ui nil
"Non-nil means using a non-intrusive `org-export-dispatch'.
In that case, no help buffer is displayed. Though, an indicator
@@ -811,9 +851,10 @@ keywords are understood:
ACTION-OR-MENU is either a function or an alist.
- If it is an action, it will be called with three arguments:
- SUBTREEP, VISIBLE-ONLY and BODY-ONLY. See `org-export-as'
- for further explanations.
+ If it is an action, it will be called with four
+ arguments (booleans): ASYNC, SUBTREEP, VISIBLE-ONLY and
+ BODY-ONLY. See `org-export-as' for further explanations on
+ some of them.
If it is an alist, associations should follow the
pattern:
@@ -1910,15 +1951,13 @@ Return transcoded string."
(cond
;; Ignored element/object.
((memq data (plist-get info :ignore-list)) nil)
- ;; Plain text. All residual text properties from parse
- ;; tree (i.e. `:parent' property) are removed.
+ ;; Plain text.
((eq type 'plain-text)
- (org-no-properties
- (org-export-filter-apply-functions
- (plist-get info :filter-plain-text)
- (let ((transcoder (org-export-transcoder data info)))
- (if transcoder (funcall transcoder data info) data))
- info)))
+ (org-export-filter-apply-functions
+ (plist-get info :filter-plain-text)
+ (let ((transcoder (org-export-transcoder data info)))
+ (if transcoder (funcall transcoder data info) data))
+ info))
;; Uninterpreted element/object: change it back to Org
;; syntax and export again resulting raw string.
((not (org-export--interpret-p data info))
@@ -2533,7 +2572,7 @@ Return the updated communication channel."
;; but a copy of it (with the same buffer-local variables and
;; visibility), where macros and include keywords are expanded and
;; Babel blocks are executed, if appropriate.
-;; `org-export-with-current-buffer-copy' macro prepares that copy.
+;; `org-export-with-buffer-copy' macro prepares that copy.
;;
;; File inclusion is taken care of by
;; `org-export-expand-include-keyword' and
@@ -2588,7 +2627,7 @@ Return code as a string."
;; Initialize communication channel with original buffer
;; attributes, unavailable in its copy.
(let ((info (org-export--get-buffer-attributes)) tree)
- (org-export-with-current-buffer-copy
+ (org-export-with-buffer-copy
;; Run first hook with current back-end as argument.
(run-hook-with-args 'org-export-before-processing-hook backend)
;; Update communication channel and get parse tree. Buffer
@@ -2645,11 +2684,14 @@ Return code as a string."
(or (org-export-data tree info) "")))
(template (cdr (assq 'template
(plist-get info :translate-alist))))
- (output (org-export-filter-apply-functions
- (plist-get info :filter-final-output)
- (if (or (not (functionp template)) body-only) body
- (funcall template body info))
- info)))
+ ;; Remove all text properties since they cannot be
+ ;; retrieved from an external process.
+ (output (org-no-properties
+ (org-export-filter-apply-functions
+ (plist-get info :filter-final-output)
+ (if (or (not (functionp template)) body-only) body
+ (funcall template body info))
+ info))))
;; Maybe add final OUTPUT to kill ring, then return it.
(when (and org-export-copy-to-kill-ring (org-string-nw-p output))
(org-kill-new output))
@@ -2752,32 +2794,94 @@ determined."
((file-name-absolute-p base-name) (concat base-name extension))
(t (concat (file-name-as-directory ".") base-name extension)))))
-(defmacro org-export-with-current-buffer-copy (&rest body)
+(defun org-export-copy-buffer ()
+ "Return a copy of the current buffer.
+The copy preserves Org buffer-local variables, visibility and
+narrowing."
+ (let ((copy-buffer-fun (org-export--generate-copy-script (current-buffer)))
+ (new-buf (generate-new-buffer (buffer-name))))
+ (with-current-buffer new-buf
+ (funcall copy-buffer-fun)
+ (set-buffer-modified-p nil))
+ new-buf))
+
+(defmacro org-export-with-buffer-copy (&rest body)
"Apply BODY in a copy of the current buffer.
-
-The copy preserves local variables and visibility of the original
-buffer.
-
-Point is at buffer's beginning when BODY is applied."
- (declare (debug (body)))
- (org-with-gensyms (original-buffer offset buffer-string overlays region)
- `(let* ((,original-buffer (current-buffer))
- (,region (list (point-min) (point-max)))
- (,buffer-string (org-with-wide-buffer (buffer-string)))
- (,overlays (mapcar 'copy-overlay (apply 'overlays-in ,region))))
- (with-temp-buffer
- (let ((buffer-invisibility-spec nil))
- (org-clone-local-variables
- ,original-buffer
- "^\\(org-\\|orgtbl-\\|major-mode$\\|outline-\\(regexp\\|level\\)$\\)")
- (insert ,buffer-string)
- (apply 'narrow-to-region ,region)
- (mapc (lambda (ov)
- (move-overlay
- ov (overlay-start ov) (overlay-end ov) (current-buffer)))
- ,overlays)
- (goto-char (point-min))
- (progn ,@body))))))
+The copy preserves local variables, visibility and contents of
+the original buffer. Point is at the beginning of the buffer
+when BODY is applied."
+ (declare (debug t))
+ (org-with-gensyms (buf-copy)
+ `(let ((,buf-copy (org-export-copy-buffer)))
+ (unwind-protect
+ (with-current-buffer ,buf-copy
+ (goto-char (point-min))
+ (progn ,@body))
+ (and (buffer-live-p ,buf-copy)
+ ;; Kill copy without confirmation.
+ (progn (with-current-buffer ,buf-copy
+ (restore-buffer-modified-p nil))
+ (kill-buffer ,buf-copy)))))))
+
+(defun org-export--generate-copy-script (buffer)
+ "Generate a function duplicating BUFFER.
+
+The copy will preserve local variables, visibility, contents and
+narrowing of the original buffer. If a region was active in
+BUFFER, contents will be narrowed to that region instead.
+
+The resulting function can be eval'ed at a later time, from
+another buffer, effectively cloning the original buffer there."
+ (with-current-buffer buffer
+ `(lambda ()
+ (let ((inhibit-modification-hooks t))
+ ;; Buffer local variables.
+ ,@(let (local-vars)
+ (mapc
+ (lambda (entry)
+ (when (consp entry)
+ (let ((var (car entry))
+ (val (cdr entry)))
+ (and (not (eq var 'org-font-lock-keywords))
+ (or (memq var
+ '(major-mode default-directory
+ buffer-file-name outline-level
+ outline-regexp
+ buffer-invisibility-spec))
+ (string-match "^\\(org-\\|orgtbl-\\)"
+ (symbol-name var)))
+ ;; Skip unreadable values, as they cannot be
+ ;; sent to external process.
+ (or (not val) (ignore-errors (read (format "%S" val))))
+ (push `(set (make-local-variable (quote ,var))
+ (quote ,val))
+ local-vars)))))
+ (buffer-local-variables (buffer-base-buffer)))
+ local-vars)
+ ;; Whole buffer contents.
+ (insert
+ ,(org-with-wide-buffer
+ (buffer-substring-no-properties
+ (point-min) (point-max))))
+ ;; Narrowing.
+ ,(if (org-region-active-p)
+ `(narrow-to-region ,(region-beginning) ,(region-end))
+ `(narrow-to-region ,(point-min) ,(point-max)))
+ ;; Current position of point.
+ (goto-char ,(point))
+ ;; Overlays with invisible property.
+ ,@(let (ov-set)
+ (mapc
+ (lambda (ov)
+ (let ((invis-prop (overlay-get ov 'invisible)))
+ (when invis-prop
+ (push `(overlay-put
+ (make-overlay ,(overlay-start ov)
+ ,(overlay-end ov))
+ 'invisible (quote ,invis-prop))
+ ov-set))))
+ (overlays-in (point-min) (point-max)))
+ ov-set)))))
(defun org-export-expand-include-keyword (&optional included dir)
"Expand every include keyword in buffer.
@@ -2935,7 +3039,7 @@ This function will return an error if the current buffer is
visiting a file."
;; Get a pristine copy of current buffer so Babel references can be
;; properly resolved.
- (let* (clone-buffer-hook (reference (clone-buffer)))
+ (let ((reference (org-export-copy-buffer)))
(unwind-protect (let ((org-current-export-file reference))
(org-export-blocks-preprocess))
(kill-buffer reference))))
@@ -4855,6 +4959,253 @@ to `:default' encoding. If it fails, return S."
+;;; Asynchronous Export
+;;
+;; `org-export-async-start' is the entry point for asynchronous
+;; export. It recreates current buffer (including visibility,
+;; narrowing and visited file) in an external Emacs process, and
+;; evaluates a command there. It then applies a function on the
+;; returned results in the current process.
+;;
+;; Asynchronously generated results are never displayed directly.
+;; Instead, they are stored in `org-export-stack-contents'. They can
+;; then be retrieved by calling `org-export-stack'.
+;;
+;; Export Stack is viewed through a dedicated major mode
+;;`org-export-stack-mode' and tools: `org-export--stack-refresh',
+;;`org-export--stack-delete', `org-export--stack-view' and
+;;`org-export--stack-clear'.
+;;
+;; For back-ends, `org-export-add-to-stack' add a new source to stack.
+;; It should used whenever `org-export-async-start' is called.
+
+(defmacro org-export-async-start (fun &rest body)
+ "Call function FUN on the results returned by BODY evaluation.
+
+BODY evaluation happens in an asynchronous process, from a buffer
+which is an exact copy of the current one.
+
+Use `org-export-add-to-stack' in FUN in order to register results
+in the stack. Examples for, respectively a temporary buffer and
+a file are:
+
+ \(org-export-async-start
+ \(lambda (output)
+ \(with-current-buffer (get-buffer-create \"*Org BACKEND Export*\")
+ \(erase-buffer)
+ \(insert output)
+ \(goto-char (point-min))
+ \(org-export-add-to-stack (current-buffer) 'backend)))
+ `(org-export-as 'backend ,subtreep ,visible-only ,body-only ',ext-plist))
+
+and
+
+ \(org-export-async-start
+ \(lambda (f) (org-export-add-to-stack f 'backend))
+ `(expand-file-name
+ \(org-export-to-file
+ 'backend ,outfile ,subtreep ,visible-only ,body-only ',ext-plist)))"
+ (declare (indent 1) (debug t))
+ (org-with-gensyms (process temp-file copy-fun proc-buffer handler)
+ ;; Write the full sexp evaluating BODY in a copy of the current
+ ;; buffer to a temporary file, as it may be too long for program
+ ;; args in `start-process'.
+ `(with-temp-message "Initializing asynchronous export process"
+ (let ((,copy-fun (org-export--generate-copy-script (current-buffer)))
+ (,temp-file (make-temp-file "org-export-process")))
+ (with-temp-file ,temp-file
+ (insert
+ (format
+ "%S"
+ `(with-temp-buffer
+ ,(when org-export-async-debug '(setq debug-on-error t))
+ ;; Initialize `org-mode' in the external process.
+ (org-mode)
+ ;; Re-create current buffer there.
+ (funcall ,,copy-fun)
+ (restore-buffer-modified-p nil)
+ ;; Sexp to evaluate in the buffer.
+ (print (progn ,,@body))))))
+ ;; Start external process.
+ (let* ((process-connection-type nil)
+ (,proc-buffer (generate-new-buffer-name "*Org Export Process*"))
+ (,process
+ (start-process
+ "org-export-process" ,proc-buffer
+ (expand-file-name invocation-name invocation-directory)
+ "-Q" "--batch"
+ "-l" org-export-async-init-file
+ "-l" ,temp-file)))
+ ;; Register running process in stack.
+ (org-export-add-to-stack (get-buffer ,proc-buffer) nil ,process)
+ ;; Set-up sentinel in order to catch results.
+ (set-process-sentinel
+ ,process
+ (let ((handler #',fun))
+ `(lambda (p status)
+ (let ((proc-buffer (process-buffer p)))
+ (when (eq (process-status p) 'exit)
+ (unwind-protect
+ (if (zerop (process-exit-status p))
+ (unwind-protect
+ (let ((results
+ (with-current-buffer proc-buffer
+ (goto-char (point-max))
+ (backward-sexp)
+ (read (current-buffer)))))
+ (funcall ,handler results))
+ (unless org-export-async-debug
+ (and (get-buffer proc-buffer)
+ (kill-buffer proc-buffer))))
+ (org-export-add-to-stack proc-buffer nil p)
+ (ding)
+ (message "Process '%s' exited abnormally" p))
+ (unless org-export-async-debug
+ (delete-file ,,temp-file)))))))))))))
+
+(defun org-export-add-to-stack (source backend &optional process)
+ "Add a new result to export stack if not present already.
+
+SOURCE is a buffer or a file name containing export results.
+BACKEND is a symbol representing export back-end used to generate
+it.
+
+Entries already pointing to SOURCE and unavailable entries are
+removed beforehand. Return the new stack."
+ (setq org-export-stack-contents
+ (cons (list source backend (or process (current-time)))
+ (org-export--stack-remove source))))
+
+(defun org-export-stack ()
+ "Menu for asynchronous export results and running processes."
+ (interactive)
+ (let ((buffer (get-buffer-create "*Org Export Stack*")))
+ (set-buffer buffer)
+ (when (zerop (buffer-size)) (org-export-stack-mode))
+ (org-export--stack-refresh)
+ (pop-to-buffer buffer))
+ (message "Type \"q\" to quit, \"?\" for help"))
+
+(defun org-export--stack-source-at-point ()
+ "Return source from export results at point in stack."
+ (let ((source (car (nth (1- (org-current-line)) org-export-stack-contents))))
+ (if (not source) (error "Source unavailable, please refresh buffer")
+ (let ((source-name (if (stringp source) source (buffer-name source))))
+ (if (save-excursion
+ (beginning-of-line)
+ (looking-at (concat ".* +" (regexp-quote source-name) "$")))
+ source
+ ;; SOURCE is not consistent with current line. The stack
+ ;; view is outdated.
+ (error "Source unavailable; type `g' to update buffer"))))))
+
+(defun org-export--stack-clear ()
+ "Remove all entries from export stack."
+ (interactive)
+ (setq org-export-stack-contents nil))
+
+(defun org-export--stack-refresh (&rest dummy)
+ "Refresh the asynchronous export stack.
+DUMMY is ignored. Unavailable sources are removed from the list.
+Return the new stack."
+ (let ((inhibit-read-only t))
+ (org-preserve-lc
+ (erase-buffer)
+ (insert (concat
+ (let ((counter 0))
+ (mapconcat
+ (lambda (entry)
+ (let ((proc-p (processp (nth 2 entry))))
+ (concat
+ ;; Back-end.
+ (format " %-12s " (or (nth 1 entry) ""))
+ ;; Age.
+ (let ((data (nth 2 entry)))
+ (if proc-p (format " %6s " (process-status data))
+ ;; Compute age of the results.
+ (org-format-seconds
+ "%4h:%.2m "
+ (float-time (time-since data)))))
+ ;; Source.
+ (format " %s"
+ (let ((source (car entry)))
+ (if (stringp source) source
+ (buffer-name source)))))))
+ ;; Clear stack from exited processes, dead buffers or
+ ;; non-existent files.
+ (setq org-export-stack-contents
+ (org-remove-if-not
+ (lambda (el)
+ (if (processp (nth 2 el))
+ (buffer-live-p (process-buffer (nth 2 el)))
+ (let ((source (car el)))
+ (if (bufferp source) (buffer-live-p source)
+ (file-exists-p source)))))
+ org-export-stack-contents)) "\n")))))))
+
+(defun org-export--stack-remove (&optional source)
+ "Remove export results at point from stack.
+If optional argument SOURCE is non-nil, remove it instead."
+ (interactive)
+ (let ((source (or source (org-export--stack-source-at-point))))
+ (setq org-export-stack-contents
+ (org-remove-if (lambda (el) (equal (car el) source))
+ org-export-stack-contents))))
+
+(defun org-export--stack-view ()
+ "View export results at point in stack."
+ (interactive)
+ (let ((source (org-export--stack-source-at-point)))
+ (cond ((processp source)
+ (org-switch-to-buffer-other-window (process-buffer source)))
+ ((bufferp source) (org-switch-to-buffer-other-window source))
+ (t (org-open-file source)))))
+
+(defconst org-export-stack-mode-map
+ (let ((km (make-sparse-keymap)))
+ (define-key km " " 'next-line)
+ (define-key km "n" 'next-line)
+ (define-key km "\C-n" 'next-line)
+ (define-key km [down] 'next-line)
+ (define-key km "p" 'previous-line)
+ (define-key km "\C-p" 'previous-line)
+ (define-key km "\C-?" 'previous-line)
+ (define-key km [up] 'previous-line)
+ (define-key km "C" 'org-export--stack-clear)
+ (define-key km "v" 'org-export--stack-view)
+ (define-key km (kbd "RET") 'org-export--stack-view)
+ (define-key km "d" 'org-export--stack-remove)
+ km)
+ "Keymap for Org Export Stack.")
+
+(define-derived-mode org-export-stack-mode special-mode "Org-Stack"
+ "Mode for displaying asynchronous export stack.
+
+Type \\[org-export-stack] to visualize the asynchronous export
+stack.
+
+In an Org Export Stack buffer, use \\[org-export--stack-view] to view export output
+on current line, \\[org-export--stack-remove] to remove it from the stack and \\[org-export--stack-clear] to clear
+stack completely.
+
+Removal entries in an Org Export Stack buffer doesn't affect
+files or buffers, only view in the stack.
+
+\\{org-export-stack-mode-map}"
+ (abbrev-mode 0)
+ (auto-fill-mode 0)
+ (setq buffer-read-only t
+ buffer-undo-list t
+ truncate-lines t
+ header-line-format
+ '(:eval
+ (format " %-12s | %6s | %s" "Back-End" "Age" "Source")))
+ (add-hook 'post-command-hook 'org-export--stack-refresh nil t)
+ (set (make-local-variable 'revert-buffer-function)
+ 'org-export--stack-refresh))
+
+
+
;;; The Dispatcher
;;
;; `org-export-dispatch' is the standard interactive way to start an
@@ -4874,23 +5225,30 @@ to switch to one or the other.
When called with C-u prefix ARG, repeat the last export action,
with the same set of options used back then, on the current
-buffer."
+buffer.
+
+When called with a double universal argument, display the
+asynchronous export stack directly."
(interactive "P")
- (let* ((input (or (and arg org-export-dispatch-last-action)
- (save-window-excursion
- (unwind-protect
- ;; Store this export command.
- (setq org-export-dispatch-last-action
- (org-export-dispatch-ui
- (list org-export-initial-scope)
- nil
- org-export-dispatch-use-expert-ui))
- (and (get-buffer "*Org Export Dispatcher*")
- (kill-buffer "*Org Export Dispatcher*"))))))
+ (let* ((input
+ (cond ((equal arg '(16)) '(stack))
+ ((and arg org-export-dispatch-last-action))
+ (t (save-window-excursion
+ (unwind-protect
+ ;; Store this export command.
+ (setq org-export-dispatch-last-action
+ (org-export-dispatch-ui
+ (list org-export-initial-scope
+ (and org-export-in-background 'async))
+ nil
+ org-export-dispatch-use-expert-ui))
+ (and (get-buffer "*Org Export Dispatcher*")
+ (kill-buffer "*Org Export Dispatcher*")))))))
(action (car input))
(optns (cdr input)))
(case action
;; First handle special hard-coded actions.
+ (stack (org-export-stack))
(publish-current-file (org-e-publish-current-file (memq 'force optns)))
(publish-current-project
(org-e-publish-current-project (memq 'force optns)))
@@ -4901,11 +5259,13 @@ buffer."
org-e-publish-project-alist)
(memq 'force optns)))
(publish-all (org-e-publish-all (memq 'force optns)))
- (otherwise
- (funcall action
- (memq 'subtree optns)
- (memq 'visible optns)
- (memq 'body optns))))))
+ (otherwise (funcall action
+ ;; Return a symbol instead of a list to ease
+ ;; asynchronous export macro use.
+ (and (memq 'async optns) t)
+ (and (memq 'subtree optns) t)
+ (and (memq 'visible optns) t)
+ (and (memq 'body optns) t))))))
(defun org-export-dispatch-ui (options first-key expertp)
"Handle interface for `org-export-dispatch'.
@@ -4916,6 +5276,7 @@ export. It can contain any of the following symbols:
`subtree' restricts export to current subtree
`visible' restricts export to visible part of buffer.
`force' force publishing files.
+`async' use asynchronous export process
FIRST-KEY is the key pressed to select the first level menu. It
is nil when this menu hasn't been selected yet.
@@ -4951,10 +5312,10 @@ back to standard interface."
((numberp key-b) t)))))
(lambda (a b) (< (car a) (car b)))))
;; Compute a list of allowed keys based on the first key
- ;; pressed, if any. Some keys (?1, ?2, ?3, ?4 and ?q) are
- ;; always available.
+ ;; pressed, if any. Some keys (?1, ?2, ?3, ?4, ?5 and ?q)
+ ;; are always available.
(allowed-keys
- (nconc (list ?1 ?2 ?3 ?4)
+ (nconc (list ?1 ?2 ?3 ?4 ?5)
(if (not first-key) (org-uniquify (mapcar 'car backends))
(let (sub-menu)
(dolist (backend backends (sort (mapcar 'car sub-menu) '<))
@@ -4962,6 +5323,7 @@ back to standard interface."
(setq sub-menu (append (nth 2 backend) sub-menu))))))
(cond ((eq first-key ?P) (list ?f ?p ?x ?a))
((not first-key) (list ?P)))
+ (list ?&)
(when expertp (list ??))
(list ?q)))
;; Build the help menu for standard UI.
@@ -4971,7 +5333,8 @@ back to standard interface."
;; Options are hard-coded.
(format "Options
[%s] Body only: %s [%s] Visible only: %s
- [%s] Export scope: %s [%s] Force publishing: %s\n"
+ [%s] Export scope: %s [%s] Force publishing: %s
+ [%s] Asynchronous export: %s\n"
(funcall fontify-key "1" t)
(if (memq 'body options) "On " "Off")
(funcall fontify-key "2" t)
@@ -4979,7 +5342,9 @@ back to standard interface."
(funcall fontify-key "3" t)
(if (memq 'subtree options) "Subtree" "Buffer ")
(funcall fontify-key "4" t)
- (if (memq 'force options) "On " "Off"))
+ (if (memq 'force options) "On " "Off")
+ (funcall fontify-key "5" t)
+ (if (memq 'async options) "On " "Off"))
;; Display registered back-end entries. When a key
;; appears for the second time, do not create another
;; entry, but append its sub-menu to existing menu.
@@ -5020,6 +5385,7 @@ back to standard interface."
(funcall fontify-key "p" ?P)
(funcall fontify-key "x" ?P)
(funcall fontify-key "a" ?P))
+ (format "\[%s] Export stack\n" (funcall fontify-key "&" t))
(format "\[%s] %s"
(funcall fontify-key "q" t)
(if first-key "Main menu" "Exit")))))
@@ -5028,11 +5394,12 @@ back to standard interface."
(expert-prompt
(when expertp
(format
- "Export command (Options: %s%s%s%s) [%s]: "
+ "Export command (Options: %s%s%s%s%s) [%s]: "
(if (memq 'body options) (funcall fontify-key "b" t) "-")
(if (memq 'visible options) (funcall fontify-key "v" t) "-")
(if (memq 'subtree options) (funcall fontify-key "s" t) "-")
(if (memq 'force options) (funcall fontify-key "f" t) "-")
+ (if (memq 'async options) (funcall fontify-key "a" t) "-")
(concat allowed-keys)))))
;; With expert UI, just read key with a fancy prompt. In standard
;; UI, display an intrusive help buffer.
@@ -5085,11 +5452,13 @@ options as CDR."
;; Help key: Switch back to standard interface if
;; expert UI was active.
((eq key ??) (org-export-dispatch-ui options first-key nil))
+ ;; Switch to asynchronous export stack.
+ ((eq key ?&) '(stack))
;; Toggle export options.
- ((memq key '(?1 ?2 ?3 ?4))
+ ((memq key '(?1 ?2 ?3 ?4 ?5))
(org-export-dispatch-ui
(let ((option (case key (?1 'body) (?2 'visible) (?3 'subtree)
- (?4 'force))))
+ (?4 'force) (?5 'async))))
(if (memq option options) (remq option options)
(cons option options)))
first-key expertp))