From f22856a5c54d99867cd24c08a14bbda23d5c6229 Mon Sep 17 00:00:00 2001 From: Bastien Guerry Date: Sun, 13 Dec 2020 13:44:15 +0100 Subject: Update to Org 9.4.1 --- lisp/org/ob-C.el | 10 +- lisp/org/ob-J.el | 9 +- lisp/org/ob-R.el | 9 +- lisp/org/ob-abc.el | 4 +- lisp/org/ob-asymptote.el | 2 - lisp/org/ob-awk.el | 2 - lisp/org/ob-calc.el | 2 - lisp/org/ob-clojure.el | 340 ++-- lisp/org/ob-comint.el | 2 - lisp/org/ob-coq.el | 4 +- lisp/org/ob-core.el | 533 +++--- lisp/org/ob-css.el | 2 - lisp/org/ob-ditaa.el | 2 - lisp/org/ob-dot.el | 2 - lisp/org/ob-ebnf.el | 26 +- lisp/org/ob-emacs-lisp.el | 51 +- lisp/org/ob-eval.el | 2 - lisp/org/ob-exp.el | 8 +- lisp/org/ob-forth.el | 3 +- lisp/org/ob-fortran.el | 5 +- lisp/org/ob-gnuplot.el | 4 +- lisp/org/ob-groovy.el | 4 - lisp/org/ob-haskell.el | 86 +- lisp/org/ob-hledger.el | 5 +- lisp/org/ob-io.el | 3 - lisp/org/ob-java.el | 7 +- lisp/org/ob-js.el | 10 +- lisp/org/ob-latex.el | 12 +- lisp/org/ob-ledger.el | 2 - lisp/org/ob-lilypond.el | 13 +- lisp/org/ob-lisp.el | 2 - lisp/org/ob-lua.el | 5 +- lisp/org/ob-makefile.el | 2 - lisp/org/ob-matlab.el | 2 - lisp/org/ob-maxima.el | 5 - lisp/org/ob-mscgen.el | 5 +- lisp/org/ob-ocaml.el | 2 - lisp/org/ob-octave.el | 8 +- lisp/org/ob-org.el | 2 - lisp/org/ob-perl.el | 2 - lisp/org/ob-picolisp.el | 8 +- lisp/org/ob-plantuml.el | 107 +- lisp/org/ob-python.el | 219 ++- lisp/org/ob-ref.el | 4 +- lisp/org/ob-ruby.el | 32 +- lisp/org/ob-sass.el | 4 +- lisp/org/ob-scheme.el | 11 +- lisp/org/ob-screen.el | 17 +- lisp/org/ob-sed.el | 5 +- lisp/org/ob-shell.el | 59 +- lisp/org/ob-shen.el | 1 + lisp/org/ob-sql.el | 118 +- lisp/org/ob-sqlite.el | 7 +- lisp/org/ob-stan.el | 3 +- lisp/org/ob-table.el | 5 +- lisp/org/ob-tangle.el | 60 +- lisp/org/ob-vala.el | 2 +- lisp/org/ol-bbdb.el | 37 +- lisp/org/ol-bibtex.el | 23 +- lisp/org/ol-docview.el | 3 +- lisp/org/ol-eshell.el | 2 +- lisp/org/ol-eww.el | 15 +- lisp/org/ol-gnus.el | 32 +- lisp/org/ol-info.el | 2 +- lisp/org/ol-irc.el | 2 +- lisp/org/ol-mhe.el | 2 +- lisp/org/ol-rmail.el | 6 +- lisp/org/ol.el | 495 +++-- lisp/org/org-agenda.el | 1252 +++++++------ lisp/org/org-archive.el | 54 +- lisp/org/org-attach.el | 154 +- lisp/org/org-capture.el | 142 +- lisp/org/org-clock.el | 141 +- lisp/org/org-colview.el | 53 +- lisp/org/org-compat.el | 135 +- lisp/org/org-crypt.el | 231 ++- lisp/org/org-datetree.el | 29 +- lisp/org/org-duration.el | 52 +- lisp/org/org-element.el | 268 +-- lisp/org/org-entities.el | 4 +- lisp/org/org-faces.el | 29 +- lisp/org/org-goto.el | 38 +- lisp/org/org-habit.el | 11 +- lisp/org/org-id.el | 131 +- lisp/org/org-indent.el | 29 +- lisp/org/org-keys.el | 20 +- lisp/org/org-lint.el | 77 +- lisp/org/org-list.el | 646 ++++--- lisp/org/org-macro.el | 58 +- lisp/org/org-macs.el | 89 +- lisp/org/org-mobile.el | 11 + lisp/org/org-mouse.el | 10 +- lisp/org/org-num.el | 7 +- lisp/org/org-pcomplete.el | 36 +- lisp/org/org-plot.el | 46 +- lisp/org/org-protocol.el | 32 +- lisp/org/org-refile.el | 742 ++++++++ lisp/org/org-src.el | 88 +- lisp/org/org-table.el | 546 +++--- lisp/org/org-tempo.el | 4 +- lisp/org/org-timer.el | 19 +- lisp/org/org-version.el | 4 +- lisp/org/org.el | 4570 ++++++++++++++++++++++----------------------- lisp/org/ox-ascii.el | 17 +- lisp/org/ox-beamer.el | 2 +- lisp/org/ox-html.el | 351 ++-- lisp/org/ox-icalendar.el | 11 +- lisp/org/ox-latex.el | 88 +- lisp/org/ox-man.el | 22 +- lisp/org/ox-md.el | 345 ++-- lisp/org/ox-odt.el | 38 +- lisp/org/ox-org.el | 4 +- lisp/org/ox-publish.el | 11 +- lisp/org/ox-texinfo.el | 23 +- lisp/org/ox.el | 322 ++-- 115 files changed, 7704 insertions(+), 5768 deletions(-) create mode 100644 lisp/org/org-refile.el (limited to 'lisp/org') diff --git a/lisp/org/ob-C.el b/lisp/org/ob-C.el index 3a26bc014b2..c5155fbfcc8 100644 --- a/lisp/org/ob-C.el +++ b/lisp/org/ob-C.el @@ -182,7 +182,7 @@ or `org-babel-execute:C++' or `org-babel-execute:D'." cmdline))) ""))) (when results - (setq results (org-trim (org-remove-indentation results))) + (setq results (org-remove-indentation results)) (org-babel-reassemble-table (org-babel-result-cond (cdr (assq :result-params params)) (org-babel-read results t) @@ -232,7 +232,13 @@ its header arguments." (list ;; includes (mapconcat - (lambda (inc) (format "#include %s" inc)) + (lambda (inc) + ;; :includes '( ) gives us a list of + ;; symbols; convert those to strings. + (when (symbolp inc) (setq inc (symbol-name inc))) + (if (string-prefix-p "<" inc) + (format "#include %s" inc) + (format "#include \"%s\"" inc))) includes "\n") ;; defines (mapconcat diff --git a/lisp/org/ob-J.el b/lisp/org/ob-J.el index c0145211bd3..e66227b8df6 100644 --- a/lisp/org/ob-J.el +++ b/lisp/org/ob-J.el @@ -3,6 +3,7 @@ ;; Copyright (C) 2011-2020 Free Software Foundation, Inc. ;; Author: Oleh Krehel +;; Maintainer: Joseph Novakovich ;; Keywords: literate programming, reproducible research ;; Homepage: https://orgmode.org @@ -76,6 +77,8 @@ This function is called by `org-babel-execute-src-block'." (message "executing J source code block") (let* ((processed-params (org-babel-process-params params)) (sessionp (cdr (assq :session params))) + (sit-time (let ((sit (assq :sit params))) + (if sit (cdr sit) .1))) (full-body (org-babel-expand-body:J body params processed-params)) (tmp-script-file (org-babel-temp-file "J-src"))) @@ -86,9 +89,9 @@ This function is called by `org-babel-execute-src-block'." (with-temp-file tmp-script-file (insert full-body)) (org-babel-eval (format "%s < %s" org-babel-J-command tmp-script-file) "")) - (org-babel-J-eval-string full-body))))) + (org-babel-J-eval-string full-body sit-time))))) -(defun org-babel-J-eval-string (str) +(defun org-babel-J-eval-string (str sit-time) "Sends STR to the `j-console-cmd' session and executes it." (let ((session (j-console-ensure-session))) (with-current-buffer (process-buffer session) @@ -96,7 +99,7 @@ This function is called by `org-babel-execute-src-block'." (insert (format "\n%s\n" str)) (let ((beg (point))) (comint-send-input) - (sit-for .1) + (sit-for sit-time) (buffer-substring-no-properties beg (point-max)))))) diff --git a/lisp/org/ob-R.el b/lisp/org/ob-R.el index b52c7591ad2..5e9d35f58e2 100644 --- a/lisp/org/ob-R.el +++ b/lisp/org/ob-R.el @@ -193,7 +193,8 @@ This function is called by `org-babel-execute-src-block'." (org-babel-comint-in-buffer session (mapc (lambda (var) (end-of-line 1) (insert var) (comint-send-input nil t) - (org-babel-comint-wait-for-output session)) var-lines)) + (org-babel-comint-wait-for-output session)) + var-lines)) session)) (defun org-babel-load-session:R (session body params) @@ -459,11 +460,11 @@ last statement in BODY, as elisp." "R-specific processing of return value. Insert hline if column names in output have been requested." (if column-names-p - (cons (car result) (cons 'hline (cdr result))) + (condition-case nil + (cons (car result) (cons 'hline (cdr result))) + (error "Could not parse R result")) result)) (provide 'ob-R) - - ;;; ob-R.el ends here diff --git a/lisp/org/ob-abc.el b/lisp/org/ob-abc.el index d473118639a..4c9e83525d9 100644 --- a/lisp/org/ob-abc.el +++ b/lisp/org/ob-abc.el @@ -4,8 +4,7 @@ ;; Author: William Waites ;; Keywords: literate programming, music -;; Homepage: http://www.tardis.ed.ac.uk/wwaites -;; Version: 0.01 +;; Homepage: https://www.tardis.ed.ac.uk/~wwaites ;; This file is part of GNU Emacs. @@ -87,4 +86,5 @@ (error "ABC does not support sessions")) (provide 'ob-abc) + ;;; ob-abc.el ends here diff --git a/lisp/org/ob-asymptote.el b/lisp/org/ob-asymptote.el index bdc74b84920..da7f870d494 100644 --- a/lisp/org/ob-asymptote.el +++ b/lisp/org/ob-asymptote.el @@ -134,6 +134,4 @@ Otherwise, it is either `real', if some elements are floats, or (provide 'ob-asymptote) - - ;;; ob-asymptote.el ends here diff --git a/lisp/org/ob-awk.el b/lisp/org/ob-awk.el index 74bbc4c2be1..577878349c5 100644 --- a/lisp/org/ob-awk.el +++ b/lisp/org/ob-awk.el @@ -106,6 +106,4 @@ This function is called by `org-babel-execute-src-block'." (provide 'ob-awk) - - ;;; ob-awk.el ends here diff --git a/lisp/org/ob-calc.el b/lisp/org/ob-calc.el index 85bcf1d4132..c2937f6952b 100644 --- a/lisp/org/ob-calc.el +++ b/lisp/org/ob-calc.el @@ -105,6 +105,4 @@ (provide 'ob-calc) - - ;;; ob-calc.el ends here diff --git a/lisp/org/ob-clojure.el b/lisp/org/ob-clojure.el index 0d6d1c0a84a..299a326e429 100644 --- a/lisp/org/ob-clojure.el +++ b/lisp/org/ob-clojure.el @@ -30,80 +30,70 @@ ;; - clojure (at least 1.2.0) ;; - clojure-mode -;; - either cider or SLIME +;; - inf-clojure, cider or SLIME -;; For Cider, see https://github.com/clojure-emacs/cider +;; For clojure-mode, see https://github.com/clojure-emacs/clojure-mode +;; For cider, see https://github.com/clojure-emacs/cider +;; For inf-clojure, see https://github.com/clojure-emacs/cider ;; For SLIME, the best way to install these components is by following ;; the directions as set out by Phil Hagelberg (Technomancy) on the ;; web page: http://technomancy.us/126 ;;; Code: -(require 'cl-lib) (require 'ob) -(require 'org-macs) -(declare-function cider-jack-in "ext:cider" (&optional prompt-project cljs-too)) (declare-function cider-current-connection "ext:cider-client" (&optional type)) (declare-function cider-current-ns "ext:cider-client" ()) -(declare-function cider-repls "ext:cider-connection" (&optional type ensure)) -(declare-function nrepl--merge "ext:nrepl-client" (dict1 dict2)) +(declare-function inf-clojure "ext:inf-clojure" (cmd)) +(declare-function inf-clojure-cmd "ext:inf-clojure" (project-type)) +(declare-function inf-clojure-eval-string "ext:inf-clojure" (code)) +(declare-function inf-clojure-project-type "ext:inf-clojure" ()) (declare-function nrepl-dict-get "ext:nrepl-client" (dict key)) -(declare-function nrepl-dict-put "ext:nrepl-client" (dict key value)) -(declare-function nrepl-request:eval "ext:nrepl-client" (input callback connection &optional ns line column additional-params tooling)) (declare-function nrepl-sync-request:eval "ext:nrepl-client" (input connection &optional ns tooling)) +(declare-function sesman-start-session "ext:sesman" (system)) (declare-function slime-eval "ext:slime" (sexp &optional package)) -(defvar nrepl-sync-request-timeout) (defvar cider-buffer-ns) -(defvar sesman-system) -(defvar cider-version) (defvar org-babel-tangle-lang-exts) (add-to-list 'org-babel-tangle-lang-exts '("clojure" . "clj")) +(add-to-list 'org-babel-tangle-lang-exts '("clojurescript" . "cljs")) (defvar org-babel-default-header-args:clojure '()) -(defvar org-babel-header-args:clojure '((ns . :any) - (package . :any))) +(defvar org-babel-header-args:clojure '((ns . :any) (package . :any))) +(defvar org-babel-default-header-args:clojurescript '()) +(defvar org-babel-header-args:clojurescript '((package . :any))) -(defcustom org-babel-clojure-sync-nrepl-timeout 10 - "Timeout value, in seconds, of a Clojure sync call. -If the value is nil, timeout is disabled." - :group 'org-babel - :type 'integer - :version "26.1" - :package-version '(Org . "9.1") - :safe #'wholenump) - -(defcustom org-babel-clojure-backend - (cond ((featurep 'cider) 'cider) - (t 'slime)) +(defcustom org-babel-clojure-backend nil "Backend used to evaluate Clojure code blocks." :group 'org-babel :type '(choice + (const :tag "inf-clojure" inf-clojure) (const :tag "cider" cider) - (const :tag "SLIME" slime))) + (const :tag "slime" slime) + (const :tag "Not configured yet" nil))) (defcustom org-babel-clojure-default-ns "user" "Default Clojure namespace for source block when finding ns failed." :type 'string :group 'org-babel) -(defun org-babel-clojure-cider-current-ns () - "Like `cider-current-ns' except `cider-find-ns'." - (or cider-buffer-ns - (let ((repl-buf (cider-current-connection))) - (and repl-buf (buffer-local-value 'cider-buffer-ns repl-buf))) - org-babel-clojure-default-ns)) - (defun org-babel-expand-body:clojure (body params) "Expand BODY according to PARAMS, return the expanded body." (let* ((vars (org-babel--get-vars params)) (ns (or (cdr (assq :ns params)) - (org-babel-clojure-cider-current-ns))) + (if (eq org-babel-clojure-backend 'cider) + (or cider-buffer-ns + (let ((repl-buf (cider-current-connection))) + (and repl-buf (buffer-local-value + 'cider-buffer-ns repl-buf)))) + org-babel-clojure-default-ns))) (result-params (cdr (assq :result-params params))) (print-level nil) (print-length nil) + ;; Remove comments, they break (let [...] ...) bindings + (body (replace-regexp-in-string "^[ ]*;+.*$" "" body)) (body (org-trim (concat ;; Source block specified namespace :ns. @@ -113,7 +103,7 @@ If the value is nil, timeout is disabled." (format "(let [%s]\n%s)" (mapconcat (lambda (var) - (format "%S (quote %S)" (car var) (cdr var))) + (format "%S %S" (car var) (cdr var))) vars "\n ") body)))))) @@ -122,161 +112,141 @@ If the value is nil, timeout is disabled." (format "(clojure.pprint/pprint (do %s))" body) body))) +(defvar ob-clojure-inf-clojure-filter-out) +(defvar ob-clojure-inf-clojure-tmp-output) +(defun ob-clojure-inf-clojure-output (s) + "Store a trimmed version of S in a variable and return S." + (let ((s0 (org-trim + (replace-regexp-in-string + ob-clojure-inf-clojure-filter-out "" s)))) + (push s0 ob-clojure-inf-clojure-tmp-output)) + s) + +(defmacro ob-clojure-with-temp-expanded (expanded params &rest body) + "Run BODY on EXPANDED code block with PARAMS." + (declare (debug (body)) (indent 2)) + `(with-temp-buffer + (insert ,expanded) + (goto-char (point-min)) + (while (not (looking-at "\\s-*\\'")) + (let* ((beg (point)) + (end (progn (forward-sexp) (point))) + (exp (org-babel-expand-body:clojure + (buffer-substring beg end) ,params))) + (sit-for .1) + ,@body)))) + +(defsubst ob-clojure-string-or-list (l) + "Convert list L into a string or a list of list." + (if (and (listp l) (= (length l) 1)) + (car l) + (mapcar #'list l))) + +(defvar inf-clojure-buffer) +(defvar comint-prompt-regexp) +(defvar inf-clojure-comint-prompt-regexp) +(defun ob-clojure-eval-with-inf-clojure (expanded params) + "Evaluate EXPANDED code block with PARAMS using inf-clojure." + (condition-case nil (require 'inf-clojure) + (user-error "inf-clojure not available")) + ;; Maybe initiate the inf-clojure session + (unless (and inf-clojure-buffer + (buffer-live-p (get-buffer inf-clojure-buffer))) + (save-window-excursion + (let* ((alias (cdr (assq :alias params))) + (cmd0 (inf-clojure-cmd (inf-clojure-project-type))) + (cmd (if alias (replace-regexp-in-string + "clojure" (format "clojure -A%s" alias) + cmd0) + cmd0))) + (setq comint-prompt-regexp inf-clojure-comint-prompt-regexp) + (funcall-interactively #'inf-clojure cmd) + (goto-char (point-max)))) + (sit-for 1)) + ;; Now evaluate the code + (setq ob-clojure-inf-clojure-filter-out + (concat "^nil\\|nil$\\|\\s-*" + (or (cdr (assq :ns params)) + org-babel-clojure-default-ns) + "=>\\s-*")) + (add-hook 'comint-preoutput-filter-functions + #'ob-clojure-inf-clojure-output) + (setq ob-clojure-inf-clojure-tmp-output nil) + (ob-clojure-with-temp-expanded expanded nil + (inf-clojure-eval-string exp)) + (sit-for .5) + (remove-hook 'comint-preoutput-filter-functions + #'ob-clojure-inf-clojure-output) + ;; And return the result + (ob-clojure-string-or-list + (delete nil + (mapcar + (lambda (s) + (unless (or (equal "" s) + (string-match-p "^Clojure" s)) + s)) + (reverse ob-clojure-inf-clojure-tmp-output))))) + +(defun ob-clojure-eval-with-cider (expanded params) + "Evaluate EXPANDED code block with PARAMS using cider." + (condition-case nil (require 'cider) + (user-error "cider not available")) + (let ((connection (cider-current-connection (cdr (assq :target params)))) + (result-params (cdr (assq :result-params params))) + result0) + (unless connection (sesman-start-session 'CIDER)) + (if (not connection) + ;; Display in the result instead of using `user-error' + (setq result0 "Please reevaluate when nREPL is connected") + (ob-clojure-with-temp-expanded expanded params + (let ((response (nrepl-sync-request:eval exp connection))) + (push (or (nrepl-dict-get response "root-ex") + (nrepl-dict-get response "ex") + (nrepl-dict-get + response (if (or (member "output" result-params) + (member "pp" result-params)) + "out" + "value"))) + result0))) + (ob-clojure-string-or-list + (reverse (delete "" (mapcar (lambda (r) + (replace-regexp-in-string "nil" "" r)) + result0))))))) + +(defun ob-clojure-eval-with-slime (expanded params) + "Evaluate EXPANDED code block with PARAMS using slime." + (condition-case nil (require 'slime) + (user-error "slime not available")) + (with-temp-buffer + (insert expanded) + (slime-eval + `(swank:eval-and-grab-output + ,(buffer-substring-no-properties (point-min) (point-max))) + (cdr (assq :package params))))) + (defun org-babel-execute:clojure (body params) - "Execute a block of Clojure code with Babel. -The underlying process performed by the code block can be output -using the :show-process parameter." + "Execute a block of Clojure code with Babel." + (unless org-babel-clojure-backend + (user-error "You need to customize org-babel-clojure-backend")) (let* ((expanded (org-babel-expand-body:clojure body params)) - (response (list 'dict)) - result) - (cl-case org-babel-clojure-backend - (cider - (require 'cider) - (let ((result-params (cdr (assq :result-params params))) - (show (cdr (assq :show-process params)))) - (if (member show '(nil "no")) - ;; Run code without showing the process. - (progn - (setq response - (let ((nrepl-sync-request-timeout - org-babel-clojure-sync-nrepl-timeout)) - (nrepl-sync-request:eval expanded - (cider-current-connection)))) - (setq result - (concat - (nrepl-dict-get response - (if (or (member "output" result-params) - (member "pp" result-params)) - "out" - "value")) - (nrepl-dict-get response "ex") - (nrepl-dict-get response "root-ex") - (nrepl-dict-get response "err")))) - ;; Show the process in an output buffer/window. - (let ((process-buffer (switch-to-buffer-other-window - "*Clojure Show Process Sub Buffer*")) - status) - ;; Run the Clojure code in nREPL. - (nrepl-request:eval - expanded - (lambda (resp) - (when (member "out" resp) - ;; Print the output of the nREPL in the output buffer. - (princ (nrepl-dict-get resp "out") process-buffer)) - (when (member "ex" resp) - ;; In case there is an exception, then add it to the - ;; output buffer as well. - (princ (nrepl-dict-get resp "ex") process-buffer) - (princ (nrepl-dict-get resp "root-ex") process-buffer)) - (when (member "err" resp) - ;; In case there is an error, then add it to the - ;; output buffer as well. - (princ (nrepl-dict-get resp "err") process-buffer)) - (nrepl--merge response resp) - ;; Update the status of the nREPL output session. - (setq status (nrepl-dict-get response "status"))) - (cider-current-connection)) - - ;; Wait until the nREPL code finished to be processed. - (while (not (member "done" status)) - (nrepl-dict-put response "status" (remove "need-input" status)) - (accept-process-output nil 0.01) - (redisplay)) - - ;; Delete the show buffer & window when the processing is - ;; finalized. - (mapc #'delete-window - (get-buffer-window-list process-buffer nil t)) - (kill-buffer process-buffer) - - ;; Put the output or the value in the result section of - ;; the code block. - (setq result - (concat - (nrepl-dict-get response - (if (or (member "output" result-params) - (member "pp" result-params)) - "out" - "value")) - (nrepl-dict-get response "ex") - (nrepl-dict-get response "root-ex") - (nrepl-dict-get response "err"))))))) - (slime - (require 'slime) - (with-temp-buffer - (insert expanded) - (setq result - (slime-eval - `(swank:eval-and-grab-output - ,(buffer-substring-no-properties (point-min) (point-max))) - (cdr (assq :package params))))))) - (org-babel-result-cond (cdr (assq :result-params params)) + (result-params (cdr (assq :result-params params))) + result) + (setq result + (cond + ((eq org-babel-clojure-backend 'inf-clojure) + (ob-clojure-eval-with-inf-clojure expanded params)) + ((eq org-babel-clojure-backend 'cider) + (ob-clojure-eval-with-cider expanded params)) + ((eq org-babel-clojure-backend 'slime) + (ob-clojure-eval-with-slime expanded params)))) + (org-babel-result-cond result-params result (condition-case nil (org-babel-script-escape result) (error result))))) -(defun org-babel-clojure-initiate-session (&optional session _params) - "Initiate a session named SESSION according to PARAMS." - (when (and session (not (string= session "none"))) - (save-window-excursion - (cond - ((org-babel-comint-buffer-livep session) nil) - ;; CIDER jack-in to the Clojure project directory. - ((eq org-babel-clojure-backend 'cider) - (require 'cider) - (let ((session-buffer - (save-window-excursion - (if (version< cider-version "0.18.0") - ;; Older CIDER (without sesman) still need to use - ;; old way. - (cider-jack-in nil) ;jack-in without project - ;; New CIDER (with sesman to manage sessions). - (unless (cider-repls) - (let ((sesman-system 'CIDER)) - (call-interactively 'sesman-link-with-directory)))) - (current-buffer)))) - (when (org-babel-comint-buffer-livep session-buffer) - (sit-for .25) - session-buffer))) - ((eq org-babel-clojure-backend 'slime) - (error "Session evaluation with SLIME is not supported")) - (t - (error "Session initiate failed"))) - (get-buffer session)))) - -(defun org-babel-prep-session:clojure (session params) - "Prepare SESSION according to the header arguments specified in PARAMS." - (let ((session (org-babel-clojure-initiate-session session)) - (var-lines (org-babel-variable-assignments:clojure params))) - (when session - (org-babel-comint-in-buffer session - (dolist (var var-lines) - (insert var) - (comint-send-input nil t) - (org-babel-comint-wait-for-output session) - (sit-for .1) - (goto-char (point-max))))) - session)) - -(defun org-babel-clojure-var-to-clojure (var) - "Convert src block's VAR to Clojure variable." - (cond - ((listp var) - (replace-regexp-in-string "(" "'(" var)) - ((stringp var) - ;; Wrap Babel passed-in header argument value with quotes in Clojure. - (format "\"%s\"" var)) - (t - (format "%S" var)))) - -(defun org-babel-variable-assignments:clojure (params) - "Return a list of Clojure statements assigning the block's variables in PARAMS." - (mapcar - (lambda (pair) - (format "(def %s %s)" - (car pair) - (org-babel-clojure-var-to-clojure (cdr pair)))) - (org-babel--get-vars params))) +(defun org-babel-execute:clojurescript (body params) + "Evaluate BODY with PARAMS as ClojureScript code." + (org-babel-execute:clojure body (cons '(:target . "cljs") params))) (provide 'ob-clojure) diff --git a/lisp/org/ob-comint.el b/lisp/org/ob-comint.el index 552b7a037cf..d3484bb7c60 100644 --- a/lisp/org/ob-comint.el +++ b/lisp/org/ob-comint.el @@ -151,6 +151,4 @@ FILE exists at end of evaluation." (provide 'ob-comint) - - ;;; ob-comint.el ends here diff --git a/lisp/org/ob-coq.el b/lisp/org/ob-coq.el index 56a57cdf649..e473eac3301 100644 --- a/lisp/org/ob-coq.el +++ b/lisp/org/ob-coq.el @@ -27,7 +27,7 @@ ;; session evaluation is supported. Requires both coq.el and ;; coq-inferior.el, both of which are distributed with Coq. ;; -;; http://coq.inria.fr/ +;; https://coq.inria.fr/ ;;; Code: (require 'ob) @@ -76,3 +76,5 @@ create one. Return the initialized session." (get-buffer org-babel-coq-buffer)) (provide 'ob-coq) + +;;; ob-coq.el ends here diff --git a/lisp/org/ob-core.el b/lisp/org/ob-core.el index 7654c7ebe41..7300f239eef 100644 --- a/lisp/org/ob-core.el +++ b/lisp/org/ob-core.el @@ -38,6 +38,7 @@ (defvar org-link-file-path-type) (defvar org-src-lang-modes) (defvar org-src-preserve-indentation) +(defvar org-babel-tangle-uncomment-comments) (declare-function org-at-item-p "org-list" ()) (declare-function org-at-table-p "org" (&optional table-type)) @@ -59,6 +60,7 @@ (declare-function org-element-type "org-element" (element)) (declare-function org-entry-get "org" (pom property &optional inherit literal-nil)) (declare-function org-escape-code-in-region "org-src" (beg end)) +(declare-function org-forward-heading-same-level "org" (arg &optional invisible-ok)) (declare-function org-in-commented-heading-p "org" (&optional no-inheritance)) (declare-function org-indent-line "org" ()) (declare-function org-list-get-list-end "org-list" (item struct prevs)) @@ -67,7 +69,6 @@ (declare-function org-list-to-generic "org-list" (LIST PARAMS)) (declare-function org-list-to-lisp "org-list" (&optional delete)) (declare-function org-macro-escape-arguments "org-macro" (&rest args)) -(declare-function org-make-options-regexp "org" (kwds &optional extra)) (declare-function org-mark-ring-push "org" (&optional pos buffer)) (declare-function org-narrow-to-subtree "org" ()) (declare-function org-next-block "org" (arg &optional backward block-regexp)) @@ -78,6 +79,7 @@ (declare-function org-src-coderef-regexp "org-src" (fmt &optional label)) (declare-function org-src-get-lang-mode "org-src" (lang)) (declare-function org-table-align "org-table" ()) +(declare-function org-table-convert-region "org-table" (beg0 end0 &optional separator)) (declare-function org-table-end "org-table" (&optional table-type)) (declare-function org-table-import "org-table" (file arg)) (declare-function org-table-to-lisp "org-table" (&optional txt)) @@ -164,7 +166,6 @@ This string must include a \"%s\" which will be replaced by the results." "Non-nil means show the time the code block was evaluated in the result hash." :group 'org-babel :type 'boolean - :version "26.1" :package-version '(Org . "9.0") :safe #'booleanp) @@ -238,7 +239,8 @@ should be asked whether to allow evaluation." (if (functionp org-confirm-babel-evaluate) (funcall org-confirm-babel-evaluate ;; Language, code block body. - (nth 0 info) (nth 1 info)) + (nth 0 info) + (org-babel--expand-body info)) org-confirm-babel-evaluate)))) (cond (noeval nil) @@ -400,6 +402,7 @@ then run `org-babel-switch-to-session'." (file . :any) (file-desc . :any) (file-ext . :any) + (file-mode . ((#o755 #o555 #o444 :any))) (hlines . ((no yes))) (mkdirp . ((yes no))) (no-expand) @@ -487,11 +490,21 @@ For the format of SAFE-LIST, see `org-babel-safe-header-args'." "Regexp matching a NAME keyword.") (defconst org-babel-result-regexp - (format "^[ \t]*#\\+%s\\(?:\\[\\(?:%s \\)?\\([[:alnum:]]+\\)\\]\\)?:[ \t]*" - org-babel-results-keyword - ;; <%Y-%m-%d %H:%M:%S> - "<\\(?:[0-9]\\{4\\}-[0-1][0-9]-[0-3][0-9] \ -[0-2][0-9]\\(?::[0-5][0-9]\\)\\{2\\}\\)>") + (rx (seq bol + (zero-or-more (any "\t ")) + "#+results" + (opt "[" + ;; Time stamp part. + (opt "(" + (= 4 digit) (= 2 "-" (= 2 digit)) + " " + (= 2 digit) (= 2 ":" (= 2 digit)) + ") ") + ;; SHA1 hash. + (group (one-or-more hex-digit)) + "]") + ":" + (zero-or-more (any "\t ")))) "Regular expression used to match result lines. If the results are associated with a hash key then the hash will be saved in match group 1.") @@ -622,6 +635,17 @@ a list with the following pattern: (setf (nth 2 info) (org-babel-generate-file-param name (nth 2 info))) info)))) +(defun org-babel--expand-body (info) + "Expand noweb references in body and remove any coderefs." + (let ((coderef (nth 6 info)) + (expand + (if (org-babel-noweb-p (nth 2 info) :eval) + (org-babel-expand-noweb-references info) + (nth 1 info)))) + (if (not coderef) expand + (replace-regexp-in-string + (org-src-coderef-regexp coderef) "" expand nil nil 1)))) + ;;;###autoload (defun org-babel-execute-src-block (&optional arg info params) "Execute the current source code block. @@ -667,17 +691,7 @@ block." ((org-babel-confirm-evaluate info) (let* ((lang (nth 0 info)) (result-params (cdr (assq :result-params params))) - ;; Expand noweb references in BODY and remove any - ;; coderef. - (body - (let ((coderef (nth 6 info)) - (expand - (if (org-babel-noweb-p params :eval) - (org-babel-expand-noweb-references info) - (nth 1 info)))) - (if (not coderef) expand - (replace-regexp-in-string - (org-src-coderef-regexp coderef) "" expand nil nil 1)))) + (body (org-babel--expand-body info)) (dir (cdr (assq :dir params))) (mkdirp (cdr (assq :mkdirp params))) (default-directory @@ -721,7 +735,11 @@ block." (with-temp-file file (insert (org-babel-format-result result - (cdr (assq :sep params)))))) + (cdr (assq :sep params))))) + ;; Set file permissions if header argument + ;; `:file-mode' is provided. + (when (assq :file-mode params) + (set-file-modes file (cdr (assq :file-mode params))))) (setq result file)) ;; Possibly perform post process provided its ;; appropriate. Dynamically bind "*this*" to the @@ -1301,10 +1319,9 @@ CONTEXT specifies the context of evaluation. It can be `:eval', "Return the current in-buffer hash." (let ((result (org-babel-where-is-src-block-result nil info))) (when result - (org-with-wide-buffer - (goto-char result) - (looking-at org-babel-result-regexp) - (match-string-no-properties 1))))) + (org-with-point-at result + (let ((case-fold-search t)) (looking-at org-babel-result-regexp)) + (match-string-no-properties 1))))) (defun org-babel-hide-hash () "Hide the hash in the current results line. @@ -1312,7 +1329,8 @@ Only the initial `org-babel-hash-show' characters of the hash will remain visible." (add-to-invisibility-spec '(org-babel-hide-hash . t)) (save-excursion - (when (and (re-search-forward org-babel-result-regexp nil t) + (when (and (let ((case-fold-search t)) + (re-search-forward org-babel-result-regexp nil t)) (match-string 1)) (let* ((start (match-beginning 1)) (hide-start (+ org-babel-hash-show start)) @@ -1330,11 +1348,12 @@ Only the initial `org-babel-hash-show' characters of each hash will remain visible. This function should be called as part of the `org-mode-hook'." (save-excursion - (while (and (not org-babel-hash-show-time) - (re-search-forward org-babel-result-regexp nil t)) - (goto-char (match-beginning 0)) - (org-babel-hide-hash) - (goto-char (match-end 0))))) + (let ((case-fold-search t)) + (while (and (not org-babel-hash-show-time) + (re-search-forward org-babel-result-regexp nil t)) + (goto-char (match-beginning 0)) + (org-babel-hide-hash) + (goto-char (match-end 0)))))) (add-hook 'org-mode-hook 'org-babel-hide-all-hashes) (defun org-babel-hash-at-point (&optional point) @@ -1363,9 +1382,10 @@ portions of results lines." (interactive) (org-babel-show-result-all) (save-excursion - (while (re-search-forward org-babel-result-regexp nil t) - (save-excursion (goto-char (match-beginning 0)) - (org-babel-hide-result-toggle-maybe))))) + (let ((case-fold-search t)) + (while (re-search-forward org-babel-result-regexp nil t) + (save-excursion (goto-char (match-beginning 0)) + (org-babel-hide-result-toggle-maybe)))))) (defun org-babel-show-result-all () "Unfold all results in the current buffer." @@ -1377,52 +1397,50 @@ portions of results lines." "Toggle visibility of result at point." (interactive) (let ((case-fold-search t)) - (if (save-excursion - (beginning-of-line 1) - (looking-at org-babel-result-regexp)) - (progn (org-babel-hide-result-toggle) - t) ;; to signal that we took action - nil))) ;; to signal that we did not + (and (org-match-line org-babel-result-regexp) + (progn (org-babel-hide-result-toggle) t)))) (defun org-babel-hide-result-toggle (&optional force) "Toggle the visibility of the current result." (interactive) (save-excursion (beginning-of-line) - (if (re-search-forward org-babel-result-regexp nil t) - (let ((start (progn (beginning-of-line 2) (- (point) 1))) - (end (progn - (while (looking-at org-babel-multi-line-header-regexp) - (forward-line 1)) - (goto-char (- (org-babel-result-end) 1)) (point))) - ov) - (if (memq t (mapcar (lambda (overlay) - (eq (overlay-get overlay 'invisible) - 'org-babel-hide-result)) - (overlays-at start))) - (when (or (not force) (eq force 'off)) - (mapc (lambda (ov) - (when (member ov org-babel-hide-result-overlays) - (setq org-babel-hide-result-overlays - (delq ov org-babel-hide-result-overlays))) - (when (eq (overlay-get ov 'invisible) - 'org-babel-hide-result) - (delete-overlay ov))) - (overlays-at start))) - (setq ov (make-overlay start end)) - (overlay-put ov 'invisible 'org-babel-hide-result) - ;; make the block accessible to isearch - (overlay-put - ov 'isearch-open-invisible - (lambda (ov) - (when (member ov org-babel-hide-result-overlays) - (setq org-babel-hide-result-overlays - (delq ov org-babel-hide-result-overlays))) - (when (eq (overlay-get ov 'invisible) - 'org-babel-hide-result) - (delete-overlay ov)))) - (push ov org-babel-hide-result-overlays))) - (error "Not looking at a result line")))) + (let ((case-fold-search t)) + (unless (re-search-forward org-babel-result-regexp nil t) + (error "Not looking at a result line"))) + (let ((start (progn (beginning-of-line 2) (1- (point)))) + (end (progn + (while (looking-at org-babel-multi-line-header-regexp) + (forward-line 1)) + (goto-char (1- (org-babel-result-end))) + (point))) + ov) + (if (memq t (mapcar (lambda (overlay) + (eq (overlay-get overlay 'invisible) + 'org-babel-hide-result)) + (overlays-at start))) + (when (or (not force) (eq force 'off)) + (mapc (lambda (ov) + (when (member ov org-babel-hide-result-overlays) + (setq org-babel-hide-result-overlays + (delq ov org-babel-hide-result-overlays))) + (when (eq (overlay-get ov 'invisible) + 'org-babel-hide-result) + (delete-overlay ov))) + (overlays-at start))) + (setq ov (make-overlay start end)) + (overlay-put ov 'invisible 'org-babel-hide-result) + ;; make the block accessible to isearch + (overlay-put + ov 'isearch-open-invisible + (lambda (ov) + (when (member ov org-babel-hide-result-overlays) + (setq org-babel-hide-result-overlays + (delq ov org-babel-hide-result-overlays))) + (when (eq (overlay-get ov 'invisible) + 'org-babel-hide-result) + (delete-overlay ov)))) + (push ov org-babel-hide-result-overlays))))) ;; org-tab-after-check-for-cycling-hook (add-hook 'org-tab-first-hook 'org-babel-hide-result-toggle-maybe) @@ -1654,7 +1672,8 @@ Note: this function removes any hlines in TABLE." (mapcar (lambda (row) (if (listp row) (cons (or (pop rownames) "") row) - row)) table) + row)) + table) table)) (defun org-babel-pick-name (names selector) @@ -1879,9 +1898,9 @@ region is not active then the point is demarcated." (block (and start (match-string 0))) (headers (and start (match-string 4))) (stars (concat (make-string (or (org-current-level) 1) ?*) " ")) - (lower-case-p (and block + (upper-case-p (and block (let (case-fold-search) - (string-match-p "#\\+begin_src" block))))) + (string-match-p "#\\+BEGIN_SRC" block))))) (if info (mapc (lambda (place) @@ -1895,9 +1914,9 @@ region is not active then the point is demarcated." (delete-region (point-at-bol) (point-at-eol))) (insert (concat (if (looking-at "^") "" "\n") - indent (funcall (if lower-case-p 'downcase 'upcase) "#+end_src\n") + indent (if upper-case-p "#+END_SRC\n" "#+end_src\n") (if arg stars indent) "\n" - indent (funcall (if lower-case-p 'downcase 'upcase) "#+begin_src ") + indent (if upper-case-p "#+BEGIN_SRC " "#+begin_src ") lang (if (> (length headers) 1) (concat " " headers) headers) @@ -1918,14 +1937,16 @@ region is not active then the point is demarcated." (if (org-region-active-p) (mark) (point)) (point)))) (insert (concat (if (looking-at "^") "" "\n") (if arg (concat stars "\n") "") - (funcall (if lower-case-p 'downcase 'upcase) "#+begin_src ") - lang "\n" - body + (if upper-case-p "#+BEGIN_SRC " "#+begin_src ") + lang "\n" body (if (or (= (length body) 0) (string-suffix-p "\r" body) - (string-suffix-p "\n" body)) "" "\n") - (funcall (if lower-case-p 'downcase 'upcase) "#+end_src\n"))) - (goto-char start) (move-end-of-line 1))))) + (string-suffix-p "\n" body)) + "" + "\n") + (if upper-case-p "#+END_SRC\n" "#+end_src\n"))) + (goto-char start) + (move-end-of-line 1))))) (defun org-babel--insert-results-keyword (name hash) "Insert RESULTS keyword with NAME value at point. @@ -1938,7 +1959,7 @@ the results hash, or nil. Leave point before the keyword." (cond ((not hash) nil) (org-babel-hash-show-time (format "[%s %s]" - (format-time-string "<%F %T>") + (format-time-string "(%F %T)") hash)) (t (format "[%s]" hash))) ":" @@ -1964,7 +1985,7 @@ point, along with related contents. Do nothing if HASH is nil. Return a non-nil value if results were cleared. In this case, leave point where new results should be inserted." (when hash - (looking-at org-babel-result-regexp) + (let ((case-fold-search t)) (looking-at org-babel-result-regexp)) (unless (string= (match-string 1) hash) (let* ((e (org-element-at-point)) (post (copy-marker (org-element-property :post-affiliated e)))) @@ -2371,13 +2392,58 @@ INFO may provide the values of these header arguments (in the (org-babel-chomp result "\n")))) (t (goto-char beg) (insert result))) (setq end (copy-marker (point) t)) - ;; possibly wrap result + ;; Possibly wrap result. (cond ((assq :wrap (nth 2 info)) - (let ((name (or (cdr (assq :wrap (nth 2 info))) "results"))) - (funcall wrap (concat "#+begin_" name) - (concat "#+end_" (car (split-string name))) - nil nil (concat "{{{results(@@" name ":") "@@)}}}"))) + (let* ((full (or (cdr (assq :wrap (nth 2 info))) "results")) + (split (split-string full)) + (type (car split)) + (opening-line (concat "#+begin_" full)) + (closing-line (concat "#+end_" type))) + (cond + ;; Escape contents from "export" wrap. Wrap + ;; inline results within an export snippet with + ;; appropriate value. + ((eq t (compare-strings type nil nil "export" nil nil t)) + (let ((backend (pcase split + (`(,_) "none") + (`(,_ ,b . ,_) b)))) + (funcall wrap + opening-line closing-line + nil nil + (format "{{{results(@@%s:" + backend) "@@)}}}"))) + ;; Escape contents from "example" wrap. Mark + ;; inline results as verbatim. + ((eq t (compare-strings type nil nil "example" nil nil t)) + (funcall wrap + opening-line closing-line + nil nil + "{{{results(=" "=)}}}")) + ;; Escape contents from "src" wrap. Mark + ;; inline results as inline source code. + ((eq t (compare-strings type nil nil "src" nil nil t)) + (let ((inline-open + (pcase split + (`(,_) + "{{{results(src_none{") + (`(,_ ,language) + (format "{{{results(src_%s{" language)) + (`(,_ ,language . ,rest) + (let ((r (mapconcat #'identity rest " "))) + (format "{{{results(src_%s[%s]{" + language r)))))) + (funcall wrap + opening-line closing-line + nil nil + inline-open "})}}}"))) + ;; Do not escape contents in non-verbatim + ;; blocks. Return plain inline results. + (t + (funcall wrap + opening-line closing-line + t nil + "{{{results(" ")}}}"))))) ((member "html" result-params) (funcall wrap "#+begin_export html" "#+end_export" nil nil "{{{results(@@html:" "@@)}}}")) @@ -2433,11 +2499,12 @@ INFO may provide the values of these header arguments (in the (defun org-babel-remove-result (&optional info keep-keyword) "Remove the result of the current source block." (interactive) - (let ((location (org-babel-where-is-src-block-result nil info))) + (let ((location (org-babel-where-is-src-block-result nil info)) + (case-fold-search t)) (when location (save-excursion (goto-char location) - (when (looking-at (concat org-babel-result-regexp ".*$")) + (when (looking-at org-babel-result-regexp) (delete-region (if keep-keyword (line-beginning-position 2) (save-excursion @@ -2488,7 +2555,7 @@ in the buffer." (if (memq (org-element-type element) ;; Possible results types. '(drawer example-block export-block fixed-width item - plain-list src-block table)) + plain-list special-block src-block table)) (save-excursion (goto-char (min (point-max) ;for narrowed buffers (org-element-property :end element))) @@ -2502,16 +2569,19 @@ If the `default-directory' is different from the containing file's directory then expand relative links." (when (stringp result) (let ((same-directory? - (and buffer-file-name + (and (buffer-file-name (buffer-base-buffer)) (not (string= (expand-file-name default-directory) - (expand-file-name - (file-name-directory buffer-file-name))))))) + (expand-file-name + (file-name-directory + (buffer-file-name (buffer-base-buffer))))))))) (format "[[file:%s]%s]" - (if (and default-directory buffer-file-name same-directory?) + (if (and default-directory + (buffer-file-name (buffer-base-buffer)) same-directory?) (if (eq org-link-file-path-type 'adaptive) (file-relative-name (expand-file-name result default-directory) - (file-name-directory (buffer-file-name))) + (file-name-directory + (buffer-file-name (buffer-base-buffer)))) (expand-file-name result default-directory)) result) (if description (concat "[" description "]") ""))))) @@ -2707,117 +2777,110 @@ would set the value of argument \"a\" equal to \"9\". Note that these arguments are not evaluated in the current source-code block but are passed literally to the \"example-block\"." (let* ((parent-buffer (or parent-buffer (current-buffer))) - (info (or info (org-babel-get-src-block-info 'light))) + (info (or info (org-babel-get-src-block-info 'light))) (lang (nth 0 info)) (body (nth 1 info)) - (ob-nww-start org-babel-noweb-wrap-start) - (ob-nww-end org-babel-noweb-wrap-end) - (new-body "") - (nb-add (lambda (text) (setq new-body (concat new-body text)))) - index source-name evaluate prefix) - (with-temp-buffer - (setq-local org-babel-noweb-wrap-start ob-nww-start) - (setq-local org-babel-noweb-wrap-end ob-nww-end) - (insert body) (goto-char (point-min)) - (setq index (point)) - (while (and (re-search-forward (org-babel-noweb-wrap) nil t)) - (save-match-data (setf source-name (match-string 1))) - (save-match-data (setq evaluate (string-match "(.*)" source-name))) - (save-match-data - (setq prefix - (buffer-substring (match-beginning 0) - (save-excursion - (beginning-of-line 1) (point))))) - ;; add interval to new-body (removing noweb reference) - (goto-char (match-beginning 0)) - (funcall nb-add (buffer-substring index (point))) - (goto-char (match-end 0)) - (setq index (point)) - (funcall - nb-add - (with-current-buffer parent-buffer - (save-restriction - (widen) - (mapconcat ;; Interpose PREFIX between every line. - #'identity - (split-string - (if evaluate - (let ((raw (org-babel-ref-resolve source-name))) - (if (stringp raw) raw (format "%S" raw))) - (or - ;; Retrieve from the Library of Babel. - (nth 2 (assoc-string source-name org-babel-library-of-babel)) - ;; Return the contents of headlines literally. - (save-excursion - (when (org-babel-ref-goto-headline-id source-name) - (org-babel-ref-headline-body))) - ;; Find the expansion of reference in this buffer. - (save-excursion - (goto-char (point-min)) - (let* ((name-regexp - (org-babel-named-src-block-regexp-for-name - source-name)) - (comment - (string= "noweb" - (cdr (assq :comments (nth 2 info))))) - (c-wrap - (lambda (s) - ;; Comment, according to LANG mode, - ;; string S. Return new string. - (with-temp-buffer - (funcall (org-src-get-lang-mode lang)) - (comment-region (point) - (progn (insert s) (point))) - (org-trim (buffer-string))))) - (expand-body - (lambda (i) - ;; Expand body of code blocked - ;; represented by block info I. - (let ((b (if (org-babel-noweb-p (nth 2 i) :eval) - (org-babel-expand-noweb-references i) - (nth 1 i)))) - (if (not comment) b - (let ((cs (org-babel-tangle-comment-links i))) - (concat (funcall c-wrap (car cs)) "\n" - b "\n" - (funcall c-wrap (cadr cs))))))))) - (if (and (re-search-forward name-regexp nil t) - (not (org-in-commented-heading-p))) - ;; Found a source block named SOURCE-NAME. - ;; Assume it is unique; do not look after - ;; `:noweb-ref' header argument. - (funcall expand-body - (org-babel-get-src-block-info 'light)) - ;; Though luck. We go into the long process - ;; of checking each source block and expand - ;; those with a matching Noweb reference. - (let ((expansion nil)) - (org-babel-map-src-blocks nil - (unless (org-in-commented-heading-p) - (let* ((info - (org-babel-get-src-block-info 'light)) - (parameters (nth 2 info))) - (when (equal source-name - (cdr (assq :noweb-ref parameters))) - (push (funcall expand-body info) expansion) - (push (or (cdr (assq :noweb-sep parameters)) - "\n") - expansion))))) - (when expansion - (mapconcat #'identity - (nreverse (cdr expansion)) - "")))))) - ;; Possibly raise an error if named block doesn't exist. - (if (or org-babel-noweb-error-all-langs - (member lang org-babel-noweb-error-langs)) - (error "%s could not be resolved (see \ -`org-babel-noweb-error-langs')" - (org-babel-noweb-wrap source-name)) - ""))) - "[\n\r]") - (concat "\n" prefix)))))) - (funcall nb-add (buffer-substring index (point-max)))) - new-body)) + (comment (string= "noweb" (cdr (assq :comments (nth 2 info))))) + (noweb-re (format "\\(.*?\\)\\(%s\\)" + (with-current-buffer parent-buffer + (org-babel-noweb-wrap)))) + (cache nil) + (c-wrap + (lambda (s) + ;; Comment string S, according to LANG mode. Return new + ;; string. + (unless org-babel-tangle-uncomment-comments + (with-temp-buffer + (funcall (org-src-get-lang-mode lang)) + (comment-region (point) + (progn (insert s) (point))) + (org-trim (buffer-string)))))) + (expand-body + (lambda (i) + ;; Expand body of code represented by block info I. + (let ((b (if (org-babel-noweb-p (nth 2 i) :eval) + (org-babel-expand-noweb-references i) + (nth 1 i)))) + (if (not comment) b + (let ((cs (org-babel-tangle-comment-links i))) + (concat (funcall c-wrap (car cs)) "\n" + b "\n" + (funcall c-wrap (cadr cs)))))))) + (expand-references + (lambda (ref cache) + (pcase (gethash ref cache) + (`(,last . ,previous) + ;; Ignore separator for last block. + (let ((strings (list (funcall expand-body last)))) + (dolist (i previous) + (let ((parameters (nth 2 i))) + ;; Since we're operating in reverse order, first + ;; push separator, then body. + (push (or (cdr (assq :noweb-sep parameters)) "\n") + strings) + (push (funcall expand-body i) strings))) + (mapconcat #'identity strings ""))) + ;; Raise an error about missing reference, or return the + ;; empty string. + ((guard (or org-babel-noweb-error-all-langs + (member lang org-babel-noweb-error-langs))) + (error "Cannot resolve %s (see `org-babel-noweb-error-langs')" + (org-babel-noweb-wrap ref))) + (_ ""))))) + (replace-regexp-in-string + noweb-re + (lambda (m) + (with-current-buffer parent-buffer + (save-match-data + (let* ((prefix (match-string 1 m)) + (id (match-string 3 m)) + (evaluate (string-match-p "(.*)" id)) + (expansion + (cond + (evaluate + ;; Evaluation can potentially modify the buffer + ;; and invalidate the cache: reset it. + (setq cache nil) + (let ((raw (org-babel-ref-resolve id))) + (if (stringp raw) raw (format "%S" raw)))) + ;; Retrieve from the Library of Babel. + ((nth 2 (assoc-string id org-babel-library-of-babel))) + ;; Return the contents of headlines literally. + ((org-babel-ref-goto-headline-id id) + (org-babel-ref-headline-body)) + ;; Look for a source block named SOURCE-NAME. If + ;; found, assume it is unique; do not look after + ;; `:noweb-ref' header argument. + ((org-with-point-at 1 + (let ((r (org-babel-named-src-block-regexp-for-name id))) + (and (re-search-forward r nil t) + (not (org-in-commented-heading-p)) + (funcall expand-body + (org-babel-get-src-block-info t)))))) + ;; All Noweb references were cached in a previous + ;; run. Extract the information from the cache. + ((hash-table-p cache) + (funcall expand-references id cache)) + ;; Though luck. We go into the long process of + ;; checking each source block and expand those + ;; with a matching Noweb reference. Since we're + ;; going to visit all source blocks in the + ;; document, cache information about them as well. + (t + (setq cache (make-hash-table :test #'equal)) + (org-with-wide-buffer + (org-babel-map-src-blocks nil + (if (org-in-commented-heading-p) + (org-forward-heading-same-level nil t) + (let* ((info (org-babel-get-src-block-info t)) + (ref (cdr (assq :noweb-ref (nth 2 info))))) + (push info (gethash ref cache)))))) + (funcall expand-references id cache))))) + ;; Interpose PREFIX between every line. + (mapconcat #'identity + (split-string expansion "[\n\r]") + (concat "\n" prefix)))))) + body t t 2))) (defun org-babel--script-escape-inner (str) (let (in-single in-double backslash out) @@ -2931,30 +2994,41 @@ situations in which is it not appropriate." (defun org-babel--string-to-number (string) "If STRING represents a number return its value. Otherwise return nil." - (and (string-match-p "\\`-?\\([0-9]\\|\\([1-9]\\|[0-9]*\\.\\)[0-9]*\\)\\'" string) - (string-to-number string))) + (unless (or (string-match-p "\\s-" (org-trim string)) + (not (string-match-p "^[0-9-e.+ ]+$" string))) + (let ((interned-string (ignore-errors (read string)))) + (when (numberp interned-string) + interned-string)))) (defun org-babel-import-elisp-from-file (file-name &optional separator) "Read the results located at FILE-NAME into an elisp table. If the table is trivial, then return it as a scalar." - (save-window-excursion - (let ((result - (with-temp-buffer - (condition-case err - (progn - (org-table-import file-name separator) - (delete-file file-name) - (delq nil - (mapcar (lambda (row) - (and (not (eq row 'hline)) - (mapcar #'org-babel-string-read row))) - (org-table-to-lisp)))) - (error (message "Error reading results: %s" err) nil))))) - (pcase result - (`((,scalar)) scalar) - (`((,_ ,_ . ,_)) result) - (`(,scalar) scalar) - (_ result))))) + (let ((result + (with-temp-buffer + (condition-case err + (progn + (insert-file-contents file-name) + (delete-file file-name) + (let ((pmax (point-max))) + ;; If the file was empty, don't bother trying to + ;; convert the table. + (when (> pmax 1) + (org-table-convert-region (point-min) pmax separator) + (delq nil + (mapcar (lambda (row) + (and (not (eq row 'hline)) + (mapcar #'org-babel-string-read row))) + (org-table-to-lisp)))))) + (error + (display-warning 'org-babel + (format "Error reading results: %S" err) + :error) + nil))))) + (pcase result + (`((,scalar)) scalar) + (`((,_ ,_ . ,_)) result) + (`(,scalar) scalar) + (_ result)))) (defun org-babel-string-read (cell) "Strip nested \"s from around strings." @@ -3053,9 +3127,8 @@ of `org-babel-temporary-directory'." (if (eq t (car (file-attributes file))) (delete-directory file) (delete-file file))) - ;; We do not want to delete "." and "..". (directory-files org-babel-temporary-directory 'full - (rx (or (not ".") "...")))) + directory-files-no-dot-files-regexp)) (delete-directory org-babel-temporary-directory)) (error (message "Failed to remove temporary Org-babel directory %s" diff --git a/lisp/org/ob-css.el b/lisp/org/ob-css.el index b03e8fac180..190f69cbff4 100644 --- a/lisp/org/ob-css.el +++ b/lisp/org/ob-css.el @@ -43,6 +43,4 @@ CSS does not support sessions." (provide 'ob-css) - - ;;; ob-css.el ends here diff --git a/lisp/org/ob-ditaa.el b/lisp/org/ob-ditaa.el index 369a080b987..59129503e9f 100644 --- a/lisp/org/ob-ditaa.el +++ b/lisp/org/ob-ditaa.el @@ -119,6 +119,4 @@ This function is called by `org-babel-execute-src-block'." (provide 'ob-ditaa) - - ;;; ob-ditaa.el ends here diff --git a/lisp/org/ob-dot.el b/lisp/org/ob-dot.el index df83068b494..669d3cdbff1 100644 --- a/lisp/org/ob-dot.el +++ b/lisp/org/ob-dot.el @@ -87,6 +87,4 @@ This function is called by `org-babel-execute-src-block'." (provide 'ob-dot) - - ;;; ob-dot.el ends here diff --git a/lisp/org/ob-ebnf.el b/lisp/org/ob-ebnf.el index 65151bf291c..773edded452 100644 --- a/lisp/org/ob-ebnf.el +++ b/lisp/org/ob-ebnf.el @@ -5,7 +5,6 @@ ;; Author: Michael Gauland ;; Keywords: literate programming, reproducible research ;; Homepage: https://orgmode.org -;; Version: 1.00 ;; This file is part of GNU Emacs. @@ -24,18 +23,18 @@ ;;; Commentary: -;;; Org-Babel support for using ebnf2ps to generate encapsulated postscript -;;; railroad diagrams. It recognizes these arguments: -;;; -;;; :file is required; it must include the extension '.eps.' All the rules -;;; in the block will be drawn in the same file. This is done by -;;; inserting a '[' comment at the start of the block (see the -;;; documentation for ebnf-eps-buffer for more information). -;;; -;;; :style specifies a value in ebnf-style-database. This provides the -;;; ability to customize the output. The style can also specify the -;;; grammar syntax (by setting ebnf-syntax); note that only ebnf, -;;; iso-ebnf, and yacc are supported by this file. +;; Org-Babel support for using ebnf2ps to generate encapsulated postscript +;; railroad diagrams. It recognizes these arguments: +;; +;; :file is required; it must include the extension '.eps.' All the rules +;; in the block will be drawn in the same file. This is done by +;; inserting a '[' comment at the start of the block (see the +;; documentation for ebnf-eps-buffer for more information). +;; +;; :style specifies a value in ebnf-style-database. This provides the +;; ability to customize the output. The style can also specify the +;; grammar syntax (by setting ebnf-syntax); note that only ebnf, +;; iso-ebnf, and yacc are supported by this file. ;;; Requirements: @@ -78,4 +77,5 @@ This function is called by `org-babel-execute-src-block'." result))) (provide 'ob-ebnf) + ;;; ob-ebnf.el ends here diff --git a/lisp/org/ob-emacs-lisp.el b/lisp/org/ob-emacs-lisp.el index 095fbdb4f54..a18038112b4 100644 --- a/lisp/org/ob-emacs-lisp.el +++ b/lisp/org/ob-emacs-lisp.el @@ -61,31 +61,30 @@ by `org-edit-src-code'.") (defun org-babel-execute:emacs-lisp (body params) "Execute a block of emacs-lisp code with Babel." - (save-window-excursion - (let* ((lexical (cdr (assq :lexical params))) - (result-params (cdr (assq :result-params params))) - (body (format (if (member "output" result-params) - "(with-output-to-string %s\n)" - "(progn %s\n)") - (org-babel-expand-body:emacs-lisp body params))) - (result (eval (read (if (or (member "code" result-params) - (member "pp" result-params)) - (concat "(pp " body ")") - body)) - (org-babel-emacs-lisp-lexical lexical)))) - (org-babel-result-cond result-params - (let ((print-level nil) - (print-length nil)) - (if (or (member "scalar" result-params) - (member "verbatim" result-params)) - (format "%S" result) - (format "%s" result))) - (org-babel-reassemble-table - result - (org-babel-pick-name (cdr (assq :colname-names params)) - (cdr (assq :colnames params))) - (org-babel-pick-name (cdr (assq :rowname-names params)) - (cdr (assq :rownames params)))))))) + (let* ((lexical (cdr (assq :lexical params))) + (result-params (cdr (assq :result-params params))) + (body (format (if (member "output" result-params) + "(with-output-to-string %s\n)" + "(progn %s\n)") + (org-babel-expand-body:emacs-lisp body params))) + (result (eval (read (if (or (member "code" result-params) + (member "pp" result-params)) + (concat "(pp " body ")") + body)) + (org-babel-emacs-lisp-lexical lexical)))) + (org-babel-result-cond result-params + (let ((print-level nil) + (print-length nil)) + (if (or (member "scalar" result-params) + (member "verbatim" result-params)) + (format "%S" result) + (format "%s" result))) + (org-babel-reassemble-table + result + (org-babel-pick-name (cdr (assq :colname-names params)) + (cdr (assq :colnames params))) + (org-babel-pick-name (cdr (assq :rowname-names params)) + (cdr (assq :rownames params))))))) (defun org-babel-emacs-lisp-lexical (lexical) "Interpret :lexical source block argument. @@ -108,6 +107,4 @@ corresponding :lexical source block argument." (provide 'ob-emacs-lisp) - - ;;; ob-emacs-lisp.el ends here diff --git a/lisp/org/ob-eval.el b/lisp/org/ob-eval.el index a939d934d94..c11ebf19e93 100644 --- a/lisp/org/ob-eval.el +++ b/lisp/org/ob-eval.el @@ -144,6 +144,4 @@ This buffer is named by `org-babel-error-buffer-name'." (provide 'ob-eval) - - ;;; ob-eval.el ends here diff --git a/lisp/org/ob-exp.el b/lisp/org/ob-exp.el index bbf9b55a300..46506fcfddc 100644 --- a/lisp/org/ob-exp.el +++ b/lisp/org/ob-exp.el @@ -33,6 +33,7 @@ (declare-function org-escape-code-in-string "org-src" (s)) (declare-function org-export-copy-buffer "ox" ()) (declare-function org-in-commented-heading-p "org" (&optional no-inheritance)) +(declare-function org-in-archived-heading-p "org" (&optional no-inheritance)) (defvar org-src-preserve-indentation) @@ -157,7 +158,8 @@ this template." ;; encountered. (goto-char (point-min)) (while (re-search-forward regexp nil t) - (unless (save-match-data (org-in-commented-heading-p)) + (unless (save-match-data (or (org-in-commented-heading-p) + (org-in-archived-heading-p))) (let* ((object? (match-end 1)) (element (save-match-data (if object? (org-element-context) @@ -403,9 +405,7 @@ inhibit insertion of results into the buffer." (`lob (save-excursion (goto-char (nth 5 info)) - (let (org-confirm-babel-evaluate) - (org-babel-execute-src-block nil info))))))))) - + (org-babel-execute-src-block nil info)))))))) (provide 'ob-exp) diff --git a/lisp/org/ob-forth.el b/lisp/org/ob-forth.el index aef6784ca48..b6191c30719 100644 --- a/lisp/org/ob-forth.el +++ b/lisp/org/ob-forth.el @@ -76,7 +76,8 @@ This function is called by `org-babel-execute-src-block'." ;; Report errors. (org-babel-eval-error-notify 1 (buffer-substring - (+ (match-beginning 0) 1) (point-max))) nil)))) + (+ (match-beginning 0) 1) (point-max))) + nil)))) (split-string (org-trim (org-babel-expand-body:generic body params)) "\n" diff --git a/lisp/org/ob-fortran.el b/lisp/org/ob-fortran.el index 154465f28e1..279ca6ceba1 100644 --- a/lisp/org/ob-fortran.el +++ b/lisp/org/ob-fortran.el @@ -101,12 +101,13 @@ its header arguments." (concat ;; variables (mapconcat 'org-babel-fortran-var-to-fortran vars "\n") - body) params) + body) + params) body) "\n") "\n"))) (defun org-babel-fortran-ensure-main-wrap (body params) "Wrap body in a \"program ... end program\" block if none exists." - (if (string-match "^[ \t]*program[ \t]*.*" (capitalize body)) + (if (string-match "^[ \t]*program\\>" (capitalize body)) (let ((vars (org-babel--get-vars params))) (when vars (error "Cannot use :vars if `program' statement is present")) body) diff --git a/lisp/org/ob-gnuplot.el b/lisp/org/ob-gnuplot.el index d11c55f7590..62ab04d94a7 100644 --- a/lisp/org/ob-gnuplot.el +++ b/lisp/org/ob-gnuplot.el @@ -35,7 +35,7 @@ ;; - gnuplot :: http://www.gnuplot.info/ ;; -;; - gnuplot-mode :: http://cars9.uchicago.edu/~ravel/software/gnuplot-mode.html +;; - gnuplot-mode :: you can search the web for the latest active one. ;;; Code: (require 'ob) @@ -278,6 +278,4 @@ Pass PARAMS through to `orgtbl-to-generic' when exporting TABLE." (provide 'ob-gnuplot) - - ;;; ob-gnuplot.el ends here diff --git a/lisp/org/ob-groovy.el b/lisp/org/ob-groovy.el index 38e2a169cee..caf35350c5c 100644 --- a/lisp/org/ob-groovy.el +++ b/lisp/org/ob-groovy.el @@ -65,7 +65,6 @@ This function is called by `org-babel-execute-src-block'." (cdr (assq :rowname-names params)) (cdr (assq :rownames params)))))) (defvar org-babel-groovy-wrapper-method - "class Runner extends Script { def out = new PrintWriter(new ByteArrayOutputStream()) def run() { %s } @@ -74,7 +73,6 @@ This function is called by `org-babel-execute-src-block'." println(new Runner().run()) ") - (defun org-babel-groovy-evaluate (session body &optional result-type result-params) "Evaluate BODY in external Groovy process. @@ -111,6 +109,4 @@ supported in Groovy." (provide 'ob-groovy) - - ;;; ob-groovy.el ends here diff --git a/lisp/org/ob-haskell.el b/lisp/org/ob-haskell.el index e004a3405e4..84e2d6c42bc 100644 --- a/lisp/org/ob-haskell.el +++ b/lisp/org/ob-haskell.el @@ -23,20 +23,19 @@ ;;; Commentary: -;; Org-Babel support for evaluating haskell source code. This one will -;; be sort of tricky because haskell programs must be compiled before +;; Org Babel support for evaluating Haskell source code. +;; Haskell programs must be compiled before ;; they can be run, but haskell code can also be run through an ;; interactive interpreter. ;; -;; For now lets only allow evaluation using the haskell interpreter. +;; By default we evaluate using the Haskell interpreter. +;; To use the compiler, specify :compile yes in the header. ;;; Requirements: -;; - haskell-mode :: http://www.iro.umontreal.ca/~monnier/elisp/#haskell-mode -;; -;; - inf-haskell :: http://www.iro.umontreal.ca/~monnier/elisp/#haskell-mode -;; -;; - (optionally) lhs2tex :: http://people.cs.uu.nl/andres/lhs2tex/ +;; - haskell-mode: http://www.iro.umontreal.ca/~monnier/elisp/#haskell-mode +;; - inf-haskell: http://www.iro.umontreal.ca/~monnier/elisp/#haskell-mode +;; - (optionally) lhs2tex: http://people.cs.uu.nl/andres/lhs2tex/ ;;; Code: (require 'ob) @@ -47,6 +46,7 @@ (declare-function run-haskell "ext:inf-haskell" (&optional arg)) (declare-function inferior-haskell-load-file "ext:inf-haskell" (&optional reload)) +(declare-function org-entry-get "org" (pom property &optional inherit literal-nil)) (defvar org-babel-tangle-lang-exts) (add-to-list 'org-babel-tangle-lang-exts '("haskell" . "hs")) @@ -60,8 +60,63 @@ (defvar haskell-prompt-regexp) -(defun org-babel-execute:haskell (body params) - "Execute a block of Haskell code." +(defcustom org-babel-haskell-compiler "ghc" + "Command used to compile a Haskell source code file into an executable. +May be either a command in the path, like \"ghc\" or an absolute +path name, like \"/usr/local/bin/ghc\". The command can include +a parameter, such as \"ghc -v\"." + :group 'org-babel + :package-version '(Org "9.4") + :type 'string) + +(defconst org-babel-header-args:haskell '(compile . :any) + "Haskell-specific header arguments.") + +(defun org-babel-haskell-execute (body params) + "This function should only be called by `org-babel-execute:haskell'" + (let* ((tmp-src-file (org-babel-temp-file "Haskell-src-" ".hs")) + (tmp-bin-file + (org-babel-process-file-name + (org-babel-temp-file "Haskell-bin-" org-babel-exeext))) + (cmdline (cdr (assq :cmdline params))) + (cmdline (if cmdline (concat " " cmdline) "")) + (flags (cdr (assq :flags params))) + (flags (mapconcat #'identity + (if (listp flags) + flags + (list flags)) + " ")) + (libs (org-babel-read + (or (cdr (assq :libs params)) + (org-entry-get nil "libs" t)) + nil)) + (libs (mapconcat #'identity + (if (listp libs) libs (list libs)) + " "))) + (with-temp-file tmp-src-file (insert body)) + (org-babel-eval + (format "%s -o %s %s %s %s" + org-babel-haskell-compiler + tmp-bin-file + flags + (org-babel-process-file-name tmp-src-file) + libs) + "") + (let ((results (org-babel-eval (concat tmp-bin-file cmdline) ""))) + (when results + (setq results (org-trim (org-remove-indentation results))) + (org-babel-reassemble-table + (org-babel-result-cond (cdr (assq :result-params params)) + (org-babel-read results t) + (let ((tmp-file (org-babel-temp-file "Haskell-"))) + (with-temp-file tmp-file (insert results)) + (org-babel-import-elisp-from-file tmp-file))) + (org-babel-pick-name + (cdr (assq :colname-names params)) (cdr (assq :colnames params))) + (org-babel-pick-name + (cdr (assq :rowname-names params)) (cdr (assq :rownames params)))))))) + +(defun org-babel-interpret-haskell (body params) (require 'inf-haskell) (add-hook 'inferior-haskell-hook (lambda () @@ -87,7 +142,7 @@ (org-babel-reassemble-table (let ((result (pcase result-type - (`output (mapconcat #'identity (reverse (cdr results)) "\n")) + (`output (mapconcat #'identity (reverse results) "\n")) (`value (car results))))) (org-babel-result-cond (cdr (assq :result-params params)) result (org-babel-script-escape result))) @@ -96,6 +151,13 @@ (org-babel-pick-name (cdr (assq :rowname-names params)) (cdr (assq :rowname-names params)))))) +(defun org-babel-execute:haskell (body params) + "Execute a block of Haskell code." + (let ((compile (string= "yes" (cdr (assq :compile params))))) + (if (not compile) + (org-babel-interpret-haskell body params) + (org-babel-haskell-execute body params)))) + (defun org-babel-haskell-initiate-session (&optional _session _params) "Initiate a haskell session. If there is not a current inferior-process-buffer in SESSION @@ -215,6 +277,4 @@ constructs (header arguments, no-web syntax etc...) are ignored." (provide 'ob-haskell) - - ;;; ob-haskell.el ends here diff --git a/lisp/org/ob-hledger.el b/lisp/org/ob-hledger.el index 06d03b6754d..ff451befa26 100644 --- a/lisp/org/ob-hledger.el +++ b/lisp/org/ob-hledger.el @@ -30,6 +30,8 @@ ;; probably ~/.hledger.journal (it may not notice your $LEDGER_FILE env var). ;; So make ~/.hledger.journal a symbolic link to the real file if necessary. +;; TODO Unit tests are more than welcome, too. + ;;; Code: (require 'ob) @@ -64,7 +66,4 @@ This function is called by `org-babel-execute-src-block'." (provide 'ob-hledger) - - ;;; ob-hledger.el ends here -;; TODO Unit tests are more than welcome, too. diff --git a/lisp/org/ob-io.el b/lisp/org/ob-io.el index 4d1f15429d2..46e721b7ae7 100644 --- a/lisp/org/ob-io.el +++ b/lisp/org/ob-io.el @@ -90,7 +90,6 @@ in BODY as elisp." raw (org-babel-script-escape raw))))))) - (defun org-babel-prep-session:io (_session _params) "Prepare SESSION according to the header arguments specified in PARAMS." (error "Sessions are not (yet) supported for Io")) @@ -103,6 +102,4 @@ supported in Io." (provide 'ob-io) - - ;;; ob-io.el ends here diff --git a/lisp/org/ob-java.el b/lisp/org/ob-java.el index 4b3d454898b..f5edc6d53b5 100644 --- a/lisp/org/ob-java.el +++ b/lisp/org/ob-java.el @@ -58,6 +58,7 @@ parameters may be used, like javac -verbose" (src-file (concat classname ".java")) (cmpflag (or (cdr (assq :cmpflag params)) "")) (cmdline (or (cdr (assq :cmdline params)) "")) + (cmdargs (or (cdr (assq :cmdargs params)) "")) (full-body (org-babel-expand-body:generic body params))) (with-temp-file src-file (insert full-body)) (org-babel-eval @@ -66,10 +67,10 @@ parameters may be used, like javac -verbose" (unless (or (not packagename) (file-exists-p packagename)) (make-directory packagename 'parents)) (let ((results (org-babel-eval (concat org-babel-java-command - " " cmdline " " classname) ""))) + " " cmdline " " classname " " cmdargs) ""))) (org-babel-reassemble-table (org-babel-result-cond (cdr (assq :result-params params)) - (org-babel-read results) + (org-babel-read results t) (let ((tmp-file (org-babel-temp-file "c-"))) (with-temp-file tmp-file (insert results)) (org-babel-import-elisp-from-file tmp-file))) @@ -80,6 +81,4 @@ parameters may be used, like javac -verbose" (provide 'ob-java) - - ;;; ob-java.el ends here diff --git a/lisp/org/ob-js.el b/lisp/org/ob-js.el index 8f66d102074..0879e689255 100644 --- a/lisp/org/ob-js.el +++ b/lisp/org/ob-js.el @@ -30,11 +30,11 @@ ;;; Requirements: -;; - a non-browser javascript engine such as node.js http://nodejs.org/ -;; or mozrepl http://wiki.github.com/bard/mozrepl/ +;; - a non-browser javascript engine such as node.js https://nodejs.org/ +;; or mozrepl https://wiki.github.com/bard/mozrepl/ ;; ;; - for session based evaluation mozrepl and moz.el are required see -;; http://wiki.github.com/bard/mozrepl/emacs-integration for +;; https://wiki.github.com/bard/mozrepl/emacs-integration for ;; configuration instructions ;;; Code: @@ -65,7 +65,7 @@ :safe #'stringp) (defvar org-babel-js-function-wrapper - "require('sys').print(require('sys').inspect(function(){\n%s\n}()));" + "require('process').stdout.write(require('util').inspect(function(){%s}()));" "Javascript code to print value of body.") (defun org-babel-execute:js (body params) @@ -201,6 +201,4 @@ then create. Return the initialized session." (provide 'ob-js) - - ;;; ob-js.el ends here diff --git a/lisp/org/ob-latex.el b/lisp/org/ob-latex.el index e0cc1033beb..4b343dd14d6 100644 --- a/lisp/org/ob-latex.el +++ b/lisp/org/ob-latex.el @@ -84,7 +84,8 @@ (regexp-quote (format "%S" (car pair))) (if (stringp (cdr pair)) (cdr pair) (format "%S" (cdr pair))) - body))) (org-babel--get-vars params)) + body))) + (org-babel--get-vars params)) (org-trim body)) (defun org-babel-execute:latex (body params) @@ -108,8 +109,11 @@ This function is called by `org-babel-execute-src-block'." (append (cdr (assq :packages params)) org-latex-packages-alist))) (cond ((and (string-suffix-p ".png" out-file) (not imagemagick)) - (org-create-formula-image - body out-file org-format-latex-options in-buffer)) + (let ((org-format-latex-header + (concat org-format-latex-header "\n" + (mapconcat #'identity headers "\n")))) + (org-create-formula-image + body out-file org-format-latex-options in-buffer))) ((string-suffix-p ".tikz" out-file) (when (file-exists-p out-file) (delete-file out-file)) (with-temp-file out-file @@ -221,6 +225,6 @@ This function is called by `org-babel-execute-src-block'." "Return an error because LaTeX doesn't support sessions." (error "LaTeX does not support sessions")) - (provide 'ob-latex) + ;;; ob-latex.el ends here diff --git a/lisp/org/ob-ledger.el b/lisp/org/ob-ledger.el index e63e10608c3..2be1a39ac7c 100644 --- a/lisp/org/ob-ledger.el +++ b/lisp/org/ob-ledger.el @@ -65,6 +65,4 @@ called by `org-babel-execute-src-block'." (provide 'ob-ledger) - - ;;; ob-ledger.el ends here diff --git a/lisp/org/ob-lilypond.el b/lisp/org/ob-lilypond.el index eb3372fa7bf..af66cc8a011 100644 --- a/lisp/org/ob-lilypond.el +++ b/lisp/org/ob-lilypond.el @@ -67,12 +67,15 @@ the midi file is not automatically played. Default value is t") (defvar org-babel-lilypond-ly-command "" "Command to execute lilypond on your system. Do not set it directly. Customize `org-babel-lilypond-commands' instead.") + (defvar org-babel-lilypond-pdf-command "" "Command to show a PDF file on your system. Do not set it directly. Customize `org-babel-lilypond-commands' instead.") + (defvar org-babel-lilypond-midi-command "" "Command to play a MIDI file on your system. Do not set it directly. Customize `org-babel-lilypond-commands' instead.") + (defcustom org-babel-lilypond-commands (cond ((eq system-type 'darwin) @@ -94,7 +97,8 @@ you can leave the string empty on this case." :version "24.4" :package-version '(Org . "8.2.7") :set - (lambda (_symbol value) + (lambda (symbol value) + (set symbol value) (setq org-babel-lilypond-ly-command (nth 0 value) org-babel-lilypond-pdf-command (nth 1 value) @@ -201,7 +205,7 @@ If error in compilation, attempt to mark the error in lilypond org file." (delete-file org-babel-lilypond-temp-file)) (rename-file org-babel-lilypond-tangled-file org-babel-lilypond-temp-file)) - (switch-to-buffer-other-window "*lilypond*") + (org-switch-to-buffer-other-window "*lilypond*") (erase-buffer) (org-babel-lilypond-compile-lilyfile org-babel-lilypond-temp-file) (goto-char (point-min)) @@ -258,7 +262,7 @@ FILE-NAME is full path to lilypond file." "Mark the erroneous lines in the lilypond org buffer. FILE-NAME is full path to lilypond file. LINE is the erroneous line." - (switch-to-buffer-other-window + (org-switch-to-buffer-other-window (concat (file-name-nondirectory (org-babel-lilypond-switch-extension file-name ".org")))) (let ((temp (point))) @@ -387,7 +391,8 @@ If TEST is non-nil, the shell command is returned and is not run." (defun org-babel-lilypond-switch-extension (file-name ext) "Utility command to swap current FILE-NAME extension with EXT." (concat (file-name-sans-extension - file-name) ext)) + file-name) + ext)) (defun org-babel-lilypond-get-header-args (mode) "Default arguments to use when evaluating a lilypond source block. diff --git a/lisp/org/ob-lisp.el b/lisp/org/ob-lisp.el index f0e1ff63572..8b126b26f20 100644 --- a/lisp/org/ob-lisp.el +++ b/lisp/org/ob-lisp.el @@ -122,6 +122,4 @@ a property list containing the parameters of the block." (provide 'ob-lisp) - - ;;; ob-lisp.el ends here diff --git a/lisp/org/ob-lua.el b/lisp/org/ob-lua.el index b046b54b1d7..5e7b5145fa2 100644 --- a/lisp/org/ob-lua.el +++ b/lisp/org/ob-lua.el @@ -107,7 +107,8 @@ VARS contains resolved variable references." (org-babel-comint-in-buffer session (mapc (lambda (var) (end-of-line 1) (insert var) (comint-send-input) - (org-babel-comint-wait-for-output session)) var-lines)) + (org-babel-comint-wait-for-output session)) + var-lines)) session)) (defun org-babel-load-session:lua (session body params) @@ -397,6 +398,4 @@ fd:close()" (provide 'ob-lua) - - ;;; ob-lua.el ends here diff --git a/lisp/org/ob-makefile.el b/lisp/org/ob-makefile.el index 15bf6ee8308..d1f2fba9894 100644 --- a/lisp/org/ob-makefile.el +++ b/lisp/org/ob-makefile.el @@ -43,6 +43,4 @@ does not support sessions." (provide 'ob-makefile) - - ;;; ob-makefile.el ends here diff --git a/lisp/org/ob-matlab.el b/lisp/org/ob-matlab.el index 958357f328b..0d238a44f75 100644 --- a/lisp/org/ob-matlab.el +++ b/lisp/org/ob-matlab.el @@ -42,6 +42,4 @@ (provide 'ob-matlab) - - ;;; ob-matlab.el ends here diff --git a/lisp/org/ob-maxima.el b/lisp/org/ob-maxima.el index 5d38cc301ad..c30b702a1ed 100644 --- a/lisp/org/ob-maxima.el +++ b/lisp/org/ob-maxima.el @@ -27,9 +27,7 @@ ;; Org-Babel support for evaluating maxima entries. ;; ;; This differs from most standard languages in that -;; ;; 1) there is no such thing as a "session" in maxima -;; ;; 2) we are adding the "cmdline" header argument ;;; Code: @@ -125,9 +123,6 @@ of the same value." (concat "[" (mapconcat #'org-babel-maxima-elisp-to-maxima val ", ") "]") (format "%s" val))) - (provide 'ob-maxima) - - ;;; ob-maxima.el ends here diff --git a/lisp/org/ob-mscgen.el b/lisp/org/ob-mscgen.el index fa4d3e3ac34..2bd9144f4fe 100644 --- a/lisp/org/ob-mscgen.el +++ b/lisp/org/ob-mscgen.el @@ -68,8 +68,7 @@ mscgen supported formats." (let* ((out-file (or (cdr (assq :file params)) "output.png" )) (filetype (or (cdr (assq :filetype params)) "png" ))) (unless (cdr (assq :file params)) - (error " -ERROR: no output file specified. Add \":file name.png\" to the src header")) + (error "ERROR: no output file specified. Add \":file name.png\" to the src header")) (org-babel-eval (concat "mscgen -T " filetype " -o " out-file) body) nil)) ;; signal that output has already been written to file @@ -79,6 +78,4 @@ ERROR: no output file specified. Add \":file name.png\" to the src header")) (provide 'ob-mscgen) - - ;;; ob-msc.el ends here diff --git a/lisp/org/ob-ocaml.el b/lisp/org/ob-ocaml.el index 6972dae2195..2389f192c5b 100644 --- a/lisp/org/ob-ocaml.el +++ b/lisp/org/ob-ocaml.el @@ -166,6 +166,4 @@ Emacs-lisp table, otherwise return the results as a string." (provide 'ob-ocaml) - - ;;; ob-ocaml.el ends here diff --git a/lisp/org/ob-octave.el b/lisp/org/ob-octave.el index fbfc9b97356..5cb47e956ff 100644 --- a/lisp/org/ob-octave.el +++ b/lisp/org/ob-octave.el @@ -136,7 +136,8 @@ specifying a variable of the same value." (org-babel-comint-in-buffer session (mapc (lambda (var) (end-of-line 1) (insert var) (comint-send-input nil t) - (org-babel-comint-wait-for-output session)) var-lines)) + (org-babel-comint-wait-for-output session)) + var-lines)) session)) (defun org-babel-matlab-initiate-session (&optional session params) @@ -230,7 +231,8 @@ value of the last statement in BODY, as elisp." org-babel-octave-eoe-indicator org-babel-octave-eoe-output) t full-body) - (insert full-body) (comint-send-input nil t)))) results) + (insert full-body) (comint-send-input nil t)))) + results) (pcase result-type (`value (org-babel-octave-import-elisp-from-file tmp-file)) @@ -259,6 +261,4 @@ This removes initial blank and comment lines and then calls (provide 'ob-octave) - - ;;; ob-octave.el ends here diff --git a/lisp/org/ob-org.el b/lisp/org/ob-org.el index 63165019a9a..858c78346f8 100644 --- a/lisp/org/ob-org.el +++ b/lisp/org/ob-org.el @@ -67,6 +67,4 @@ This function is called by `org-babel-execute-src-block'." (provide 'ob-org) - - ;;; ob-org.el ends here diff --git a/lisp/org/ob-perl.el b/lisp/org/ob-perl.el index 2daf5774195..3e784e2a0e6 100644 --- a/lisp/org/ob-perl.el +++ b/lisp/org/ob-perl.el @@ -152,6 +152,4 @@ return the value of the last statement in BODY, as elisp." (provide 'ob-perl) - - ;;; ob-perl.el ends here diff --git a/lisp/org/ob-picolisp.el b/lisp/org/ob-picolisp.el index ec2a228456a..96fee36fcb8 100644 --- a/lisp/org/ob-picolisp.el +++ b/lisp/org/ob-picolisp.el @@ -111,11 +111,11 @@ This function is called by `org-babel-execute-src-block'." (cond ((or (member "code" result-params) (member "pp" result-params)) - (format "(pretty (out \"/dev/null\" %s))" full-body)) + (format "(pretty (out \"%s\" %s))" null-device full-body)) ((and (member "value" result-params) (not session)) - (format "(print (out \"/dev/null\" %s))" full-body)) + (format "(print (out \"%s\" %s))" null-device full-body)) ((member "value" result-params) - (format "(out \"/dev/null\" %s)" full-body)) + (format "(out \"%s\" %s)" null-device full-body)) (t full-body))) (result (if (not (string= session-name "none")) @@ -182,6 +182,4 @@ then create. Return the initialized session." (provide 'ob-picolisp) - - ;;; ob-picolisp.el ends here diff --git a/lisp/org/ob-plantuml.el b/lisp/org/ob-plantuml.el index 5bf9e2beee4..e692bf7cdbe 100644 --- a/lisp/org/ob-plantuml.el +++ b/lisp/org/ob-plantuml.el @@ -26,12 +26,12 @@ ;; Org-Babel support for evaluating plantuml script. ;; ;; Inspired by Ian Yang's org-export-blocks-format-plantuml -;; http://www.emacswiki.org/emacs/org-export-blocks-format-plantuml.el +;; https://www.emacswiki.org/emacs/org-export-blocks-format-plantuml.el ;;; Requirements: ;; plantuml | http://plantuml.sourceforge.net/ -;; plantuml.jar | `org-plantuml-jar-path' should point to the jar file +;; plantuml.jar | `org-plantuml-jar-path' should point to the jar file (when exec mode is `jar') ;;; Code: (require 'ob) @@ -46,6 +46,31 @@ :version "24.1" :type 'string) +(defcustom org-plantuml-exec-mode 'jar + "Method to use for PlantUML diagram generation. +`jar' means to use java together with the JAR. +The JAR can be configured via `org-plantuml-jar-path'. + +`plantuml' means to use the PlantUML executable. +The executable can be configured via `org-plantuml-executable-path'. +You can also configure extra arguments via `org-plantuml-executable-args'." + :group 'org-babel + :package-version '(Org . "9.4") + :type 'symbol + :options '(jar plantuml)) + +(defcustom org-plantuml-executable-path "plantuml" + "File name of the PlantUML executable." + :group 'org-babel + :package-version '(Org . "9.4") + :type 'string) + +(defcustom org-plantuml-executable-args (list "-headless") + "The arguments passed to plantuml executable when executing PlantUML." + :group 'org-babel + :package-version '(Org . "9.4") + :type '(repeat string)) + (defun org-babel-variable-assignments:plantuml (params) "Return a list of PlantUML statements assigning the block's variables. PARAMS is a property list of source block parameters, which may @@ -69,10 +94,11 @@ function to convert variables to PlantUML assignments. If BODY does not contain @startXXX ... @endXXX clauses, @startuml ... @enduml will be added." - (let ((assignments (org-babel-variable-assignments:plantuml params))) - (if (string-prefix-p "@start" body t) assignments - (format "@startuml\n%s\n@enduml" - (org-babel-expand-body:generic body params assignments))))) + (let ((full-body + (org-babel-expand-body:generic + body params (org-babel-variable-assignments:plantuml params)))) + (if (string-prefix-p "@start" body t) full-body + (format "@startuml\n%s\n@enduml" full-body)))) (defun org-babel-execute:plantuml (body params) "Execute a block of plantuml code with org-babel. @@ -82,40 +108,41 @@ This function is called by `org-babel-execute-src-block'." (cmdline (cdr (assq :cmdline params))) (in-file (org-babel-temp-file "plantuml-")) (java (or (cdr (assq :java params)) "")) + (executable (cond ((eq org-plantuml-exec-mode 'plantuml) org-plantuml-executable-path) + (t "java"))) + (executable-args (cond ((eq org-plantuml-exec-mode 'plantuml) org-plantuml-executable-args) + ((string= "" org-plantuml-jar-path) + (error "`org-plantuml-jar-path' is not set")) + ((not (file-exists-p org-plantuml-jar-path)) + (error "Could not find plantuml.jar at %s" org-plantuml-jar-path)) + (t (list java + "-jar" + (shell-quote-argument (expand-file-name org-plantuml-jar-path)))))) (full-body (org-babel-plantuml-make-body body params)) - (cmd (if (string= "" org-plantuml-jar-path) - (error "`org-plantuml-jar-path' is not set") - (concat "java " java " -jar " - (shell-quote-argument - (expand-file-name org-plantuml-jar-path)) - (if (string= (file-name-extension out-file) "png") - " -tpng" "") - (if (string= (file-name-extension out-file) "svg") - " -tsvg" "") - (if (string= (file-name-extension out-file) "eps") - " -teps" "") - (if (string= (file-name-extension out-file) "pdf") - " -tpdf" "") - (if (string= (file-name-extension out-file) "tex") - " -tlatex" "") - (if (string= (file-name-extension out-file) "vdx") - " -tvdx" "") - (if (string= (file-name-extension out-file) "xmi") - " -txmi" "") - (if (string= (file-name-extension out-file) "scxml") - " -tscxml" "") - (if (string= (file-name-extension out-file) "html") - " -thtml" "") - (if (string= (file-name-extension out-file) "txt") - " -ttxt" "") - (if (string= (file-name-extension out-file) "utxt") - " -utxt" "") - " -p " cmdline " < " - (org-babel-process-file-name in-file) - " > " - (org-babel-process-file-name out-file))))) - (unless (file-exists-p org-plantuml-jar-path) - (error "Could not find plantuml.jar at %s" org-plantuml-jar-path)) + (cmd (mapconcat #'identity + (append + (list executable) + executable-args + (pcase (file-name-extension out-file) + ("png" '("-tpng")) + ("svg" '("-tsvg")) + ("eps" '("-teps")) + ("pdf" '("-tpdf")) + ("tex" '("-tlatex")) + ("vdx" '("-tvdx")) + ("xmi" '("-txmi")) + ("scxml" '("-tscxml")) + ("html" '("-thtml")) + ("txt" '("-ttxt")) + ("utxt" '("-utxt"))) + (list + "-p" + cmdline + "<" + (org-babel-process-file-name in-file) + ">" + (org-babel-process-file-name out-file))) + " "))) (with-temp-file in-file (insert full-body)) (message "%s" cmd) (org-babel-eval cmd "") nil)) ;; signal that output has already been written to file @@ -126,6 +153,4 @@ This function is called by `org-babel-execute-src-block'." (provide 'ob-plantuml) - - ;;; ob-plantuml.el ends here diff --git a/lisp/org/ob-python.el b/lisp/org/ob-python.el index 823f6e63d57..ffb8ee855ef 100644 --- a/lisp/org/ob-python.el +++ b/lisp/org/ob-python.el @@ -4,6 +4,7 @@ ;; Authors: Eric Schulte ;; Dan Davison +;; Maintainer: Jack Kamm ;; Keywords: literate programming, reproducible research ;; Homepage: https://orgmode.org @@ -29,10 +30,11 @@ ;;; Code: (require 'ob) (require 'org-macs) +(require 'python) -(declare-function py-shell "ext:python-mode" (&optional argprompt)) +(declare-function py-shell "ext:python-mode" (&rest args)) (declare-function py-toggle-shells "ext:python-mode" (arg)) -(declare-function run-python "ext:python" (&optional cmd dedicated show)) +(declare-function py-shell-send-string "ext:python-mode" (strg &optional process)) (defvar org-babel-tangle-lang-exts) (add-to-list 'org-babel-tangle-lang-exts '("python" . "py")) @@ -104,7 +106,8 @@ VARS contains resolved variable references." (org-babel-comint-in-buffer session (mapc (lambda (var) (end-of-line 1) (insert var) (comint-send-input) - (org-babel-comint-wait-for-output session)) var-lines)) + (org-babel-comint-wait-for-output session)) + var-lines)) session)) (defun org-babel-load-session:python (session body params) @@ -177,42 +180,40 @@ Emacs-lisp table, otherwise return the results as a string." "Initiate a python session. If there is not a current inferior-process-buffer in SESSION then create. Return the initialized session." - (require org-babel-python-mode) (save-window-excursion (let* ((session (if session (intern session) :default)) - (python-buffer (org-babel-python-session-buffer session)) + (py-buffer (org-babel-python-session-buffer session)) (cmd (if (member system-type '(cygwin windows-nt ms-dos)) (concat org-babel-python-command " -i") org-babel-python-command))) (cond - ((and (eq 'python org-babel-python-mode) - (fboundp 'run-python)) ; python.el - (if (not (version< "24.1" emacs-version)) - (run-python cmd) - (unless python-buffer - (setq python-buffer (org-babel-python-with-earmuffs session))) - (let ((python-shell-buffer-name - (org-babel-python-without-earmuffs python-buffer))) - (run-python cmd)))) + ((eq 'python org-babel-python-mode) ; python.el + (unless py-buffer + (setq py-buffer (org-babel-python-with-earmuffs session))) + (let ((python-shell-buffer-name + (org-babel-python-without-earmuffs py-buffer))) + (run-python cmd) + (sleep-for 0 10))) ((and (eq 'python-mode org-babel-python-mode) (fboundp 'py-shell)) ; python-mode.el + (require 'python-mode) ;; Make sure that py-which-bufname is initialized, as otherwise ;; it will be overwritten the first time a Python buffer is ;; created. (py-toggle-shells py-default-interpreter) ;; `py-shell' creates a buffer whose name is the value of ;; `py-which-bufname' with '*'s at the beginning and end - (let* ((bufname (if (and python-buffer (buffer-live-p python-buffer)) + (let* ((bufname (if (and py-buffer (buffer-live-p py-buffer)) (replace-regexp-in-string ;; zap surrounding * - "^\\*\\([^*]+\\)\\*$" "\\1" python-buffer) + "^\\*\\([^*]+\\)\\*$" "\\1" py-buffer) (concat "Python-" (symbol-name session)))) (py-which-bufname bufname)) - (py-shell) - (setq python-buffer (org-babel-python-with-earmuffs bufname)))) + (setq py-buffer (org-babel-python-with-earmuffs bufname)) + (py-shell nil nil t org-babel-python-command py-buffer nil nil t nil))) (t (error "No function available for running an inferior Python"))) (setq org-babel-python-buffers - (cons (cons session python-buffer) + (cons (cons session py-buffer) (assq-delete-all session org-babel-python-buffers))) session))) @@ -222,8 +223,9 @@ then create. Return the initialized session." (org-babel-python-session-buffer (org-babel-python-initiate-session-by-key session)))) -(defvar org-babel-python-eoe-indicator "'org_babel_python_eoe'" +(defvar org-babel-python-eoe-indicator "org_babel_python_eoe" "A string to indicate that evaluation has completed.") + (defconst org-babel-python-wrapper-method " def main(): @@ -238,14 +240,39 @@ def main(): open('%s', 'w').write( pprint.pformat(main()) )") -(defconst org-babel-python--exec-tmpfile - (concat - "__org_babel_python_fname = '%s'; " - "__org_babel_python_fh = open(__org_babel_python_fname); " - "exec(compile(" - "__org_babel_python_fh.read(), __org_babel_python_fname, 'exec'" - ")); " - "__org_babel_python_fh.close()")) +(defconst org-babel-python--exec-tmpfile "\ +with open('%s') as __org_babel_python_tmpfile: + exec(compile(__org_babel_python_tmpfile.read(), __org_babel_python_tmpfile.name, 'exec'))" + "Template for Python session command with output results. + +Has a single %s escape, the tempfile containing the source code +to evaluate.") + +(defun org-babel-python-format-session-value + (src-file result-file result-params) + "Return Python code to evaluate SRC-FILE and write result to RESULT-FILE." + (format "\ +import ast +with open('%s') as __org_babel_python_tmpfile: + __org_babel_python_ast = ast.parse(__org_babel_python_tmpfile.read()) +__org_babel_python_final = __org_babel_python_ast.body[-1] +if isinstance(__org_babel_python_final, ast.Expr): + __org_babel_python_ast.body = __org_babel_python_ast.body[:-1] + exec(compile(__org_babel_python_ast, '', 'exec')) + __org_babel_python_final = eval(compile(ast.Expression( + __org_babel_python_final.value), '', 'eval')) + with open('%s', 'w') as __org_babel_python_tmpfile: + if %s: + import pprint + __org_babel_python_tmpfile.write(pprint.pformat(__org_babel_python_final)) + else: + __org_babel_python_tmpfile.write(str(__org_babel_python_final)) +else: + exec(compile(__org_babel_python_ast, '', 'exec')) + __org_babel_python_final = None" + (org-babel-process-file-name src-file 'noquote) + (org-babel-process-file-name result-file 'noquote) + (if (member "pp" result-params) "True" "False"))) (defun org-babel-python-evaluate (session body &optional result-type result-params preamble) @@ -256,6 +283,19 @@ open('%s', 'w').write( pprint.pformat(main()) )") (org-babel-python-evaluate-external-process body result-type result-params preamble))) +(defun org-babel-python--shift-right (body &optional count) + (with-temp-buffer + (python-mode) + (insert body) + (goto-char (point-min)) + (while (not (eobp)) + (unless (python-syntax-context 'string) + (python-indent-shift-right (line-beginning-position) + (line-end-position) + count)) + (forward-line 1)) + (buffer-string))) + (defun org-babel-python-evaluate-external-process (body &optional result-type result-params preamble) "Evaluate BODY in external python process. @@ -276,89 +316,70 @@ last statement in BODY, as elisp." (if (member "pp" result-params) org-babel-python-pp-wrapper-method org-babel-python-wrapper-method) - (mapconcat - (lambda (line) (format "\t%s" line)) - (split-string (org-remove-indentation (org-trim body)) - "[\r\n]") - "\n") + (org-babel-python--shift-right body) (org-babel-process-file-name tmp-file 'noquote)))) (org-babel-eval-read-file tmp-file)))))) (org-babel-result-cond result-params raw (org-babel-python-table-or-string (org-trim raw))))) +(defun org-babel-python--send-string (session body) + "Pass BODY to the Python process in SESSION. +Return output." + (with-current-buffer session + (let* ((string-buffer "") + (comint-output-filter-functions + (cons (lambda (text) (setq string-buffer + (concat string-buffer text))) + comint-output-filter-functions)) + (body (format "\ +try: +%s +except: + raise +finally: + print('%s')" + (org-babel-python--shift-right body 4) + org-babel-python-eoe-indicator))) + (if (not (eq 'python-mode org-babel-python-mode)) + (let ((python-shell-buffer-name + (org-babel-python-without-earmuffs session))) + (python-shell-send-string body)) + (require 'python-mode) + (py-shell-send-string body (get-buffer-process session))) + ;; same as `python-shell-comint-end-of-output-p' in emacs-25.1+ + (while (not (string-match + org-babel-python-eoe-indicator + string-buffer)) + (accept-process-output (get-buffer-process (current-buffer)))) + (org-babel-chomp (substring string-buffer 0 (match-beginning 0)))))) + (defun org-babel-python-evaluate-session (session body &optional result-type result-params) "Pass BODY to the Python process in SESSION. If RESULT-TYPE equals `output' then return standard output as a string. If RESULT-TYPE equals `value' then return the value of the last statement in BODY, as elisp." - (let* ((send-wait (lambda () (comint-send-input nil t) (sleep-for 0 5))) - (dump-last-value - (lambda - (tmp-file pp) - (mapc - (lambda (statement) (insert statement) (funcall send-wait)) - (if pp - (list - "import pprint" - (format "open('%s', 'w').write(pprint.pformat(_))" - (org-babel-process-file-name tmp-file 'noquote))) - (list (format "open('%s', 'w').write(str(_))" - (org-babel-process-file-name tmp-file - 'noquote))))))) - (last-indent 0) - (input-body (lambda (body) - (dolist (line (split-string body "[\r\n]")) - ;; Insert a blank line to end an indent - ;; block. - (let ((curr-indent (string-match "\\S-" line))) - (if curr-indent - (progn - (when (< curr-indent last-indent) - (insert "") - (funcall send-wait)) - (setq last-indent curr-indent)) - (setq last-indent 0))) - (insert line) - (funcall send-wait)) - (funcall send-wait))) + (let* ((tmp-src-file (org-babel-temp-file "python-")) (results - (pcase result-type - (`output - (let ((body (if (string-match-p ".\n+." body) ; Multiline - (let ((tmp-src-file (org-babel-temp-file - "python-"))) - (with-temp-file tmp-src-file (insert body)) - (format org-babel-python--exec-tmpfile - tmp-src-file)) - body))) - (mapconcat - #'org-trim - (butlast - (org-babel-comint-with-output - (session org-babel-python-eoe-indicator t body) - (funcall input-body body) - (funcall send-wait) (funcall send-wait) - (insert org-babel-python-eoe-indicator) - (funcall send-wait)) - 2) "\n"))) - (`value - (let ((tmp-file (org-babel-temp-file "python-"))) - (org-babel-comint-with-output - (session org-babel-python-eoe-indicator nil body) - (let ((comint-process-echoes nil)) - (funcall input-body body) - (funcall dump-last-value tmp-file - (member "pp" result-params)) - (funcall send-wait) (funcall send-wait) - (insert org-babel-python-eoe-indicator) - (funcall send-wait))) - (org-babel-eval-read-file tmp-file)))))) - (unless (string= (substring org-babel-python-eoe-indicator 1 -1) results) - (org-babel-result-cond result-params - results - (org-babel-python-table-or-string results))))) + (progn + (with-temp-file tmp-src-file (insert body)) + (pcase result-type + (`output + (let ((body (format org-babel-python--exec-tmpfile + (org-babel-process-file-name + tmp-src-file 'noquote)))) + (org-babel-python--send-string session body))) + (`value + (let* ((tmp-results-file (org-babel-temp-file "python-")) + (body (org-babel-python-format-session-value + tmp-src-file tmp-results-file result-params))) + (org-babel-python--send-string session body) + (sleep-for 0 10) + (org-babel-eval-read-file tmp-results-file))))))) + (org-babel-result-cond result-params + results + (org-babel-python-table-or-string results)))) (defun org-babel-python-read-string (string) "Strip \\='s from around Python string." @@ -369,6 +390,4 @@ last statement in BODY, as elisp." (provide 'ob-python) - - ;;; ob-python.el ends here diff --git a/lisp/org/ob-ref.el b/lisp/org/ob-ref.el index 19905bf6b97..394c4ffb59d 100644 --- a/lisp/org/ob-ref.el +++ b/lisp/org/ob-ref.el @@ -143,7 +143,8 @@ Emacs Lisp representation of the value of the variable." (org-babel-ref-split-args new-referent)))) (when (> (length new-header-args) 0) (setq args (append (org-babel-parse-header-arguments - new-header-args) args))) + new-header-args) + args))) (setq ref new-refere))) (when (string-match "^\\(.+\\):\\(.+\\)$" ref) (setq split-file (match-string 1 ref)) @@ -240,7 +241,6 @@ to \"0:-1\"." "Split ARG-STRING into top-level arguments of balanced parenthesis." (mapcar #'org-trim (org-babel-balanced-split arg-string 44))) - (provide 'ob-ref) ;;; ob-ref.el ends here diff --git a/lisp/org/ob-ruby.el b/lisp/org/ob-ruby.el index 90956271cf5..5ed29f8891a 100644 --- a/lisp/org/ob-ruby.el +++ b/lisp/org/ob-ruby.el @@ -30,16 +30,17 @@ ;; - ruby and irb executables :: http://www.ruby-lang.org/ ;; ;; - ruby-mode :: Can be installed through ELPA, or from -;; http://github.com/eschulte/rinari/raw/master/util/ruby-mode.el +;; https://github.com/eschulte/rinari/raw/master/util/ruby-mode.el ;; ;; - inf-ruby mode :: Can be installed through ELPA, or from -;; http://github.com/eschulte/rinari/raw/master/util/inf-ruby.el +;; https://github.com/eschulte/rinari/raw/master/util/inf-ruby.el ;;; Code: (require 'ob) (require 'org-macs) -(declare-function run-ruby "ext:inf-ruby" (&optional command name)) +(declare-function run-ruby-or-pop-to-buffer "ext:inf-ruby" (command &optional name buffer)) +(declare-function inf-ruby-buffer "ext:inf-ruby" ()) (declare-function xmp "ext:rcodetools" (&optional option)) (defvar inf-ruby-default-implementation) @@ -51,7 +52,8 @@ (defvar org-babel-default-header-args:ruby '()) (defvar org-babel-ruby-command "ruby" - "Name of command to use for executing ruby code.") + "Name of command to use for executing ruby code. +It's possible to override it by using a header argument `:ruby'") (defcustom org-babel-ruby-hline-to "nil" "Replace hlines in incoming tables with this when translating to ruby." @@ -71,9 +73,12 @@ "Execute a block of Ruby code with Babel. This function is called by `org-babel-execute-src-block'." (let* ((session (org-babel-ruby-initiate-session - (cdr (assq :session params)))) + (cdr (assq :session params)) params)) (result-params (cdr (assq :result-params params))) (result-type (cdr (assq :result-type params))) + (org-babel-ruby-command + (or (cdr (assq :ruby params)) + org-babel-ruby-command)) (full-body (org-babel-expand-body:generic body params (org-babel-variable-assignments:ruby params))) (result (if (member "xmp" result-params) @@ -103,7 +108,8 @@ This function is called by `org-babel-execute-src-block'." (mapc (lambda (var) (insert var) (comint-send-input nil t) (org-babel-comint-wait-for-output session) - (sit-for .1) (goto-char (point-max))) var-lines)) + (sit-for .1) (goto-char (point-max))) + var-lines)) session)) (defun org-babel-load-session:ruby (session body params) @@ -147,17 +153,21 @@ Emacs-lisp table, otherwise return the results as a string." res) res))) -(defun org-babel-ruby-initiate-session (&optional session _params) +(defun org-babel-ruby-initiate-session (&optional session params) "Initiate a ruby session. If there is not a current inferior-process-buffer in SESSION then create one. Return the initialized session." (unless (string= session "none") (require 'inf-ruby) - (let* ((cmd (cdr (assoc inf-ruby-default-implementation - inf-ruby-implementations))) + (let* ((cmd (cdr (or (assq :ruby params) + (assoc inf-ruby-default-implementation + inf-ruby-implementations)))) (buffer (get-buffer (format "*%s*" session))) (session-buffer (or buffer (save-window-excursion - (run-ruby cmd session) + (run-ruby-or-pop-to-buffer + cmd (or session "ruby") + (unless session + (inf-ruby-buffer))) (current-buffer))))) (if (org-babel-comint-buffer-livep session-buffer) (progn (sit-for .25) session-buffer) @@ -263,6 +273,4 @@ return the value of the last statement in BODY, as elisp." (provide 'ob-ruby) - - ;;; ob-ruby.el ends here diff --git a/lisp/org/ob-sass.el b/lisp/org/ob-sass.el index 60c081dcb38..33d8ef7e471 100644 --- a/lisp/org/ob-sass.el +++ b/lisp/org/ob-sass.el @@ -35,7 +35,7 @@ ;;; Requirements: -;; - sass-mode :: http://github.com/nex3/haml/blob/master/extra/sass-mode.el +;; - sass-mode :: https://github.com/nex3/haml/blob/master/extra/sass-mode.el ;;; Code: (require 'ob) @@ -65,6 +65,4 @@ This function is called by `org-babel-execute-src-block'." (provide 'ob-sass) - - ;;; ob-sass.el ends here diff --git a/lisp/org/ob-scheme.el b/lisp/org/ob-scheme.el index bfd53d5d8bb..3eee8213dfd 100644 --- a/lisp/org/ob-scheme.el +++ b/lisp/org/ob-scheme.el @@ -43,6 +43,7 @@ (require 'geiser-impl nil t) (defvar geiser-repl--repl) ; Defined in geiser-repl.el (defvar geiser-impl--implementation) ; Defined in geiser-impl.el +(defvar geiser-scheme-implementation) ; Defined in geiser-impl.el (defvar geiser-default-implementation) ; Defined in geiser-impl.el (defvar geiser-active-implementations) ; Defined in geiser-impl.el (defvar geiser-debug-show-debug-p) ; Defined in geiser-debug.el @@ -71,7 +72,8 @@ (defun org-babel-expand-body:scheme (body params) "Expand BODY according to PARAMS, return the expanded body." (let ((vars (org-babel--get-vars params)) - (prepends (cdr (assq :prologue params)))) + (prepends (cdr (assq :prologue params))) + (postpends (cdr (assq :epilogue params)))) (concat (and prepends (concat prepends "\n")) (if (null vars) body (format "(let (%s)\n%s\n)" @@ -80,7 +82,8 @@ (format "%S" (print `(,(car var) ',(cdr var))))) vars "\n ") - body))))) + body)) + (and postpends (concat "\n" postpends))))) (defvar org-babel-scheme-repl-map (make-hash-table :test #'equal) @@ -175,7 +178,8 @@ is true; otherwise returns the last value." (geiser-debug-show-debug-p nil)) (let ((ret (geiser-eval-region (point-min) (point-max)))) (setq result (if output - (geiser-eval--retort-output ret) + (or (geiser-eval--retort-output ret) + "Geiser Interpreter produced no output") (geiser-eval--retort-result-str ret ""))))) (when (not repl) (save-current-buffer (set-buffer repl-buffer) @@ -208,6 +212,7 @@ This function is called by `org-babel-execute-src-block'." (let* ((result-type (cdr (assq :result-type params))) (impl (or (when (cdr (assq :scheme params)) (intern (cdr (assq :scheme params)))) + geiser-scheme-implementation geiser-default-implementation (car geiser-active-implementations))) (session (org-babel-scheme-make-session-name diff --git a/lisp/org/ob-screen.el b/lisp/org/ob-screen.el index ad00ee070d4..8a11f7a3b6e 100644 --- a/lisp/org/ob-screen.el +++ b/lisp/org/ob-screen.el @@ -40,7 +40,8 @@ In case you want to use a different screen than one selected by your $PATH") (defvar org-babel-default-header-args:screen - '((:results . "silent") (:session . "default") (:cmd . "sh") (:terminal . "xterm")) + `((:results . "silent") (:session . "default") (:cmd . "sh") + (:terminal . "xterm") (:screenrc . ,null-device)) "Default arguments to use when running screen source blocks.") (defun org-babel-execute:screen (body params) @@ -59,11 +60,11 @@ In case you want to use a different screen than one selected by your $PATH") (let* ((session (cdr (assq :session params))) (cmd (cdr (assq :cmd params))) (terminal (cdr (assq :terminal params))) + (screenrc (cdr (assq :screenrc params))) (process-name (concat "org-babel: terminal (" session ")"))) (apply 'start-process process-name "*Messages*" terminal `("-T" ,(concat "org-babel: " session) "-e" ,org-babel-screen-location - "-c" "/dev/null" "-mS" ,(concat "org-babel-session-" session) - ,cmd)) + "-c" ,screenrc "-mS" ,session ,cmd)) ;; XXX: Is there a better way than the following? (while (not (org-babel-screen-session-socketname session)) ;; wait until screen session is available before returning @@ -97,9 +98,8 @@ In case you want to use a different screen than one selected by your $PATH") nil (mapcar (lambda (x) - (when (string-match - (concat "org-babel-session-" session) x) - x)) + (and (string-match-p (regexp-quote session) x) + x)) sockets))))) (when match-socket (car (split-string match-socket))))) @@ -108,6 +108,7 @@ In case you want to use a different screen than one selected by your $PATH") (let ((tmpfile (org-babel-temp-file "screen-"))) (with-temp-file tmpfile (insert body) + (insert "\n") ;; org-babel has superfluous spaces (goto-char (point-min)) @@ -126,7 +127,7 @@ The terminal should shortly flicker." ;; XXX: need to find a better way to do the following (while (not (file-readable-p tmpfile)) ;; do something, otherwise this will be optimized away - (format "org-babel-screen: File not readable yet.")) + (message "org-babel-screen: File not readable yet.")) (setq tmp-string (with-temp-buffer (insert-file-contents-literally tmpfile) (buffer-substring (point-min) (point-max)))) @@ -138,6 +139,4 @@ The terminal should shortly flicker." (provide 'ob-screen) - - ;;; ob-screen.el ends here diff --git a/lisp/org/ob-sed.el b/lisp/org/ob-sed.el index f926da890fc..6914cd3bfee 100644 --- a/lisp/org/ob-sed.el +++ b/lisp/org/ob-sed.el @@ -4,7 +4,6 @@ ;; Author: Bjarte Johansen ;; Keywords: literate programming, reproducible research -;; Version: 0.1.1 ;; This file is part of GNU Emacs. @@ -68,7 +67,8 @@ function is called by `org-babel-execute-src-block'." (in-file (cdr (assq :in-file params))) (code-file (let ((file (org-babel-temp-file "sed-"))) (with-temp-file file - (insert body)) file)) + (insert body)) + file)) (stdin (let ((stdin (cdr (assq :stdin params)))) (when stdin (let ((tmp (org-babel-temp-file "sed-stdin-")) @@ -102,4 +102,5 @@ function is called by `org-babel-execute-src-block'." (cdr (assq :rowname-names params)) (cdr (assq :rownames params)))))) (provide 'ob-sed) + ;;; ob-sed.el ends here diff --git a/lisp/org/ob-shell.el b/lisp/org/ob-shell.el index 1383f42f259..c08ba50fe7e 100644 --- a/lisp/org/ob-shell.el +++ b/lisp/org/ob-shell.el @@ -71,6 +71,19 @@ outside the Customize interface." (set-default symbol value) (org-babel-shell-initialize))) +(defcustom org-babel-shell-results-defaults-to-output t + "Let shell execution defaults to \":results output\". + +When set to t, use \":results output\" when no :results setting +is set. This is especially useful for inline source blocks. + +When set to nil, stick to the convention of using :results value +as the default setting when no :results is set, the \"value\" of +a shell execution being its exit code." + :group 'org-babel + :type 'boolean + :package-version '(Org . "9.4")) + (defun org-babel-execute:shell (body params) "Execute a block of Shell commands with Babel. This function is called by `org-babel-execute-src-block'." @@ -79,9 +92,17 @@ This function is called by `org-babel-execute-src-block'." (stdin (let ((stdin (cdr (assq :stdin params)))) (when stdin (org-babel-sh-var-to-string (org-babel-ref-resolve stdin))))) + (results-params (cdr (assq :result-params params))) + (value-is-exit-status + (or (and + (equal '("replace") results-params) + (not org-babel-shell-results-defaults-to-output)) + (member "value" results-params))) (cmdline (cdr (assq :cmdline params))) - (full-body (org-babel-expand-body:generic - body params (org-babel-variable-assignments:shell params)))) + (full-body (concat + (org-babel-expand-body:generic + body params (org-babel-variable-assignments:shell params)) + (when value-is-exit-status "\necho $?")))) (org-babel-reassemble-table (org-babel-sh-evaluate session full-body params stdin cmdline) (org-babel-pick-name @@ -96,7 +117,8 @@ This function is called by `org-babel-execute-src-block'." (org-babel-comint-in-buffer session (mapc (lambda (var) (insert var) (comint-send-input nil t) - (org-babel-comint-wait-for-output session)) var-lines)) + (org-babel-comint-wait-for-output session)) + var-lines)) session)) (defun org-babel-load-session:shell (session body params) @@ -129,15 +151,15 @@ This function is called by `org-babel-execute-src-block'." (varname values &optional sep hline) "Return a list of statements declaring the values as bash associative array." (format "unset %s\ndeclare -A %s\n%s" - varname varname - (mapconcat - (lambda (items) - (format "%s[%s]=%s" - varname - (org-babel-sh-var-to-sh (car items) sep hline) - (org-babel-sh-var-to-sh (cdr items) sep hline))) - values - "\n"))) + varname varname + (mapconcat + (lambda (items) + (format "%s[%s]=%s" + varname + (org-babel-sh-var-to-sh (car items) sep hline) + (org-babel-sh-var-to-sh (cdr items) sep hline))) + values + "\n"))) (defun org-babel--variable-assignments:bash (varname values &optional sep hline) "Represent the parameters as useful Bash shell variables." @@ -208,6 +230,12 @@ If RESULT-TYPE equals `output' then return a list of the outputs of the statements in BODY, if RESULT-TYPE equals `value' then return the value of the last statement in BODY." (let* ((shebang (cdr (assq :shebang params))) + (results-params (cdr (assq :result-params params))) + (value-is-exit-status + (or (and + (equal '("replace") results-params) + (not org-babel-shell-results-defaults-to-output)) + (member "value" results-params))) (results (cond ((or stdin cmdline) ; external shell script w/STDIN @@ -259,8 +287,9 @@ return the value of the last statement in BODY." (insert body)) (set-file-modes script-file #o755) (org-babel-eval script-file ""))) - (t - (org-babel-eval shell-file-name (org-trim body)))))) + (t (org-babel-eval shell-file-name (org-trim body)))))) + (when value-is-exit-status + (setq results (car (reverse (split-string results "\n" t))))) (when results (let ((result-params (cdr (assq :result-params params)))) (org-babel-result-cond result-params @@ -277,6 +306,4 @@ return the value of the last statement in BODY." (provide 'ob-shell) - - ;;; ob-shell.el ends here diff --git a/lisp/org/ob-shen.el b/lisp/org/ob-shen.el index 1ce7113294c..0e012ac82b0 100644 --- a/lisp/org/ob-shen.el +++ b/lisp/org/ob-shen.el @@ -75,4 +75,5 @@ This function is called by `org-babel-execute-src-block'." (error results)))))) (provide 'ob-shen) + ;;; ob-shen.el ends here diff --git a/lisp/org/ob-sql.el b/lisp/org/ob-sql.el index 59cf19568ed..7c359b988d3 100644 --- a/lisp/org/ob-sql.el +++ b/lisp/org/ob-sql.el @@ -55,7 +55,7 @@ ;; - dbi ;; - mssql ;; - sqsh -;; - postgresql +;; - postgresql (postgres) ;; - oracle ;; - vertica ;; @@ -73,6 +73,7 @@ (declare-function orgtbl-to-csv "org-table" (table params)) (declare-function org-table-to-lisp "org-table" (&optional txt)) (declare-function cygwin-convert-file-name-to-windows "cygw32.c" (file &optional absolute-p)) +(declare-function sql-set-product "sql" (product)) (defvar sql-connection-alist) (defvar org-babel-default-header-args:sql '()) @@ -92,6 +93,13 @@ (org-babel-sql-expand-vars body (org-babel--get-vars params))) +(defun org-babel-edit-prep:sql (info) + "Set `sql-product' in Org edit buffer. +Set `sql-product' in Org edit buffer according to the +corresponding :engine source block header argument." + (let ((product (cdr (assq :engine (nth 2 info))))) + (sql-set-product product))) + (defun org-babel-sql-dbstring-mysql (host port user password database) "Make MySQL cmd line args for database connection. Pass nil to omit that arg." (combine-and-quote-strings @@ -211,64 +219,64 @@ This function is called by `org-babel-execute-src-block'." (out-file (or (cdr (assq :out-file params)) (org-babel-temp-file "sql-out-"))) (header-delim "") - (command (pcase (intern engine) - (`dbi (format "dbish --batch %s < %s | sed '%s' > %s" - (or cmdline "") - (org-babel-process-file-name in-file) - "/^+/d;s/^|//;s/(NULL)/ /g;$d" - (org-babel-process-file-name out-file))) - (`monetdb (format "mclient -f tab %s < %s > %s" - (or cmdline "") - (org-babel-process-file-name in-file) - (org-babel-process-file-name out-file))) - (`mssql (format "sqlcmd %s -s \"\t\" %s -i %s -o %s" - (or cmdline "") - (org-babel-sql-dbstring-mssql - dbhost dbuser dbpassword database) - (org-babel-sql-convert-standard-filename - (org-babel-process-file-name in-file)) - (org-babel-sql-convert-standard-filename - (org-babel-process-file-name out-file)))) - (`mysql (format "mysql %s %s %s < %s > %s" - (org-babel-sql-dbstring-mysql - dbhost dbport dbuser dbpassword database) - (if colnames-p "" "-N") - (or cmdline "") - (org-babel-process-file-name in-file) - (org-babel-process-file-name out-file))) - (`postgresql (format - "%spsql --set=\"ON_ERROR_STOP=1\" %s -A -P \ -footer=off -F \"\t\" %s -f %s -o %s %s" - (if dbpassword - (format "PGPASSWORD=%s " dbpassword) - "") - (if colnames-p "" "-t") - (org-babel-sql-dbstring-postgresql - dbhost dbport dbuser database) - (org-babel-process-file-name in-file) - (org-babel-process-file-name out-file) - (or cmdline ""))) - (`sqsh (format "sqsh %s %s -i %s -o %s -m csv" + (command (cl-case (intern engine) + (dbi (format "dbish --batch %s < %s | sed '%s' > %s" + (or cmdline "") + (org-babel-process-file-name in-file) + "/^+/d;s/^|//;s/(NULL)/ /g;$d" + (org-babel-process-file-name out-file))) + (monetdb (format "mclient -f tab %s < %s > %s" + (or cmdline "") + (org-babel-process-file-name in-file) + (org-babel-process-file-name out-file))) + (mssql (format "sqlcmd %s -s \"\t\" %s -i %s -o %s" (or cmdline "") - (org-babel-sql-dbstring-sqsh + (org-babel-sql-dbstring-mssql dbhost dbuser dbpassword database) (org-babel-sql-convert-standard-filename (org-babel-process-file-name in-file)) (org-babel-sql-convert-standard-filename (org-babel-process-file-name out-file)))) - (`vertica (format "vsql %s -f %s -o %s %s" - (org-babel-sql-dbstring-vertica - dbhost dbport dbuser dbpassword database) - (org-babel-process-file-name in-file) - (org-babel-process-file-name out-file) - (or cmdline ""))) - (`oracle (format - "sqlplus -s %s < %s > %s" - (org-babel-sql-dbstring-oracle - dbhost dbport dbuser dbpassword database) - (org-babel-process-file-name in-file) - (org-babel-process-file-name out-file))) - (_ (error "No support for the %s SQL engine" engine))))) + (mysql (format "mysql %s %s %s < %s > %s" + (org-babel-sql-dbstring-mysql + dbhost dbport dbuser dbpassword database) + (if colnames-p "" "-N") + (or cmdline "") + (org-babel-process-file-name in-file) + (org-babel-process-file-name out-file))) + ((postgresql postgres) (format + "%spsql --set=\"ON_ERROR_STOP=1\" %s -A -P \ +footer=off -F \"\t\" %s -f %s -o %s %s" + (if dbpassword + (format "PGPASSWORD=%s " dbpassword) + "") + (if colnames-p "" "-t") + (org-babel-sql-dbstring-postgresql + dbhost dbport dbuser database) + (org-babel-process-file-name in-file) + (org-babel-process-file-name out-file) + (or cmdline ""))) + (sqsh (format "sqsh %s %s -i %s -o %s -m csv" + (or cmdline "") + (org-babel-sql-dbstring-sqsh + dbhost dbuser dbpassword database) + (org-babel-sql-convert-standard-filename + (org-babel-process-file-name in-file)) + (org-babel-sql-convert-standard-filename + (org-babel-process-file-name out-file)))) + (vertica (format "vsql %s -f %s -o %s %s" + (org-babel-sql-dbstring-vertica + dbhost dbport dbuser dbpassword database) + (org-babel-process-file-name in-file) + (org-babel-process-file-name out-file) + (or cmdline ""))) + (oracle (format + "sqlplus -s %s < %s > %s" + (org-babel-sql-dbstring-oracle + dbhost dbport dbuser dbpassword database) + (org-babel-process-file-name in-file) + (org-babel-process-file-name out-file))) + (t (user-error "No support for the %s SQL engine" engine))))) (with-temp-file in-file (insert (pcase (intern engine) @@ -301,7 +309,7 @@ SET COLSEP '|' (progn (insert-file-contents-literally out-file) (buffer-string))) (with-temp-buffer (cond - ((memq (intern engine) '(dbi mysql postgresql sqsh vertica)) + ((memq (intern engine) '(dbi mysql postgresql postgres sqsh vertica)) ;; Add header row delimiter after column-names header in first line (cond (colnames-p @@ -365,6 +373,4 @@ SET COLSEP '|' (provide 'ob-sql) - - ;;; ob-sql.el ends here diff --git a/lisp/org/ob-sqlite.el b/lisp/org/ob-sqlite.el index 957ee653479..22d018bcf5a 100644 --- a/lisp/org/ob-sqlite.el +++ b/lisp/org/ob-sqlite.el @@ -133,11 +133,12 @@ This function is called by `org-babel-execute-src-block'." "If RESULT looks like a trivial table, then unwrap it." (if (and (equal 1 (length result)) (equal 1 (length (car result)))) - (org-babel-read (caar result)) + (org-babel-read (caar result) t) (mapcar (lambda (row) (if (eq 'hline row) 'hline - (mapcar #'org-babel-string-read row))) result))) + (mapcar #'org-babel-string-read row))) + result))) (defun org-babel-sqlite-offset-colnames (table headers-p) "If HEADERS-P is non-nil then offset the first row as column names." @@ -152,6 +153,4 @@ Prepare SESSION according to the header arguments specified in PARAMS." (provide 'ob-sqlite) - - ;;; ob-sqlite.el ends here diff --git a/lisp/org/ob-stan.el b/lisp/org/ob-stan.el index c563a6c3e55..00aa8fb28c8 100644 --- a/lisp/org/ob-stan.el +++ b/lisp/org/ob-stan.el @@ -41,7 +41,7 @@ ;; For more information and usage examples, visit ;; https://orgmode.org/worg/org-contrib/babel/languages/ob-doc-stan.html ;; -;; [1] http://mc-stan.org/ +;; [1] https://mc-stan.org/ ;;; Code: (require 'ob) @@ -82,4 +82,5 @@ Otherwise, write the Stan code directly to the named file." (user-error "Stan does not support sessions")) (provide 'ob-stan) + ;;; ob-stan.el ends here diff --git a/lisp/org/ob-table.el b/lisp/org/ob-table.el index 3132965c702..77daf7be4ef 100644 --- a/lisp/org/ob-table.el +++ b/lisp/org/ob-table.el @@ -62,7 +62,8 @@ If STRING ends in a newline character, then remove the newline character and replace it with ellipses." (if (and (stringp string) (string-match "[\n\r]\\(.\\)?" string)) (concat (substring string 0 (match-beginning 0)) - (when (match-string 1 string) "...")) string)) + (when (match-string 1 string) "...")) + string)) (defmacro org-sbe (source-block &rest variables) "Return the results of calling SOURCE-BLOCK with VARIABLES. @@ -147,6 +148,4 @@ as shown in the example below. (provide 'ob-table) - - ;;; ob-table.el ends here diff --git a/lisp/org/ob-tangle.el b/lisp/org/ob-tangle.el index 946039869fb..b74b3fa0c49 100644 --- a/lisp/org/ob-tangle.el +++ b/lisp/org/ob-tangle.el @@ -41,6 +41,7 @@ (declare-function org-element-type "org-element" (element)) (declare-function org-heading-components "org" ()) (declare-function org-in-commented-heading-p "org" (&optional no-inheritance)) +(declare-function org-in-archived-heading-p "org" (&optional no-inheritance)) (declare-function outline-previous-heading "outline" ()) (defcustom org-babel-tangle-lang-exts @@ -166,13 +167,14 @@ evaluating BODY." (def-edebug-spec org-babel-with-temp-filebuffer (form body)) ;;;###autoload -(defun org-babel-tangle-file (file &optional target-file lang) +(defun org-babel-tangle-file (file &optional target-file lang-re) "Extract the bodies of source code blocks in FILE. Source code blocks are extracted with `org-babel-tangle'. Optional argument TARGET-FILE can be used to specify a default -export file for all source blocks. Optional argument LANG can be -used to limit the exported source code blocks by language. -Return a list whose CAR is the tangled file name." +export file for all source blocks. Optional argument LANG-RE can +be used to limit the exported source code blocks by languages +matching a regular expression. Return a list whose CAR is the +tangled file name." (interactive "fFile to tangle: \nP") (let ((visited-p (find-buffer-visiting (expand-file-name file))) to-be-removed) @@ -180,7 +182,7 @@ Return a list whose CAR is the tangled file name." (save-window-excursion (find-file file) (setq to-be-removed (current-buffer)) - (mapcar #'expand-file-name (org-babel-tangle nil target-file lang))) + (mapcar #'expand-file-name (org-babel-tangle nil target-file lang-re))) (unless visited-p (kill-buffer to-be-removed))))) @@ -192,7 +194,7 @@ Return a list whose CAR is the tangled file name." (mapc (lambda (el) (copy-file el pub-dir t)) (org-babel-tangle-file filename))) ;;;###autoload -(defun org-babel-tangle (&optional arg target-file lang) +(defun org-babel-tangle (&optional arg target-file lang-re) "Write code blocks to source-specific files. Extract the bodies of all source code blocks from the current file into their own source-specific files. @@ -200,8 +202,9 @@ With one universal prefix argument, only tangle the block at point. When two universal prefix arguments, only tangle blocks for the tangle file of the block at point. Optional argument TARGET-FILE can be used to specify a default -export file for all source blocks. Optional argument LANG can be -used to limit the exported source code blocks by language." +export file for all source blocks. Optional argument LANG-RE can +be used to limit the exported source code blocks by languages +matching a regular expression." (interactive "P") (run-hooks 'org-babel-pre-tangle-hook) ;; Possibly Restrict the buffer to the current code block @@ -286,7 +289,7 @@ used to limit the exported source code blocks by language." specs))) (if (equal arg '(4)) (org-babel-tangle-single-block 1 t) - (org-babel-tangle-collect-blocks lang tangle-file))) + (org-babel-tangle-collect-blocks lang-re tangle-file))) (message "Tangled %d code block%s from %s" block-counter (if (= block-counter 1) "" "s") (file-name-nondirectory @@ -364,13 +367,14 @@ that the appropriate major-mode is set. SPEC has the form: (org-fill-template org-babel-tangle-comment-format-end link-data))))) -(defun org-babel-tangle-collect-blocks (&optional language tangle-file) +(defun org-babel-tangle-collect-blocks (&optional lang-re tangle-file) "Collect source blocks in the current Org file. Return an association list of source-code block specifications of the form used by `org-babel-spec-to-string' grouped by language. -Optional argument LANGUAGE can be used to limit the collected -source code blocks by language. Optional argument TANGLE-FILE -can be used to limit the collected code blocks by target file." +Optional argument LANG-RE can be used to limit the collected +source code blocks by languages matching a regular expression. +Optional argument TANGLE-FILE can be used to limit the collected +code blocks by target file." (let ((counter 0) last-heading-pos blocks) (org-babel-map-src-blocks (buffer-file-name) (let ((current-heading-pos @@ -379,13 +383,14 @@ can be used to limit the collected code blocks by target file." (if (eq last-heading-pos current-heading-pos) (cl-incf counter) (setq counter 1) (setq last-heading-pos current-heading-pos))) - (unless (org-in-commented-heading-p) + (unless (or (org-in-commented-heading-p) + (org-in-archived-heading-p)) (let* ((info (org-babel-get-src-block-info 'light)) (src-lang (nth 0 info)) (src-tfile (cdr (assq :tangle (nth 2 info))))) (unless (or (string= src-tfile "no") (and tangle-file (not (equal tangle-file src-tfile))) - (and language (not (string= language src-lang)))) + (and lang-re (not (string-match-p lang-re src-lang)))) ;; Add the spec for this block to blocks under its ;; language. (let ((by-lang (assoc src-lang blocks)) @@ -471,9 +476,9 @@ non-nil, return the full association list to be used by file) (if (and org-babel-tangle-use-relative-file-links (string-match org-link-types-re link) - (string= (match-string 0 link) "file")) + (string= (match-string 1 link) "file")) (concat "file:" - (file-relative-name (match-string 1 link) + (file-relative-name (substring link (match-end 0)) (file-name-directory (cdr (assq :tangle params))))) link) @@ -513,14 +518,16 @@ which enable the original code blocks to be found." (goto-char (point-min)) (let ((counter 0) new-body end) (while (re-search-forward org-link-bracket-re nil t) - (when (re-search-forward - (concat " " (regexp-quote (match-string 2)) " ends here")) - (setq end (match-end 0)) - (forward-line -1) - (save-excursion - (when (setq new-body (org-babel-tangle-jump-to-org)) - (org-babel-update-block-body new-body))) - (setq counter (+ 1 counter))) + (if (and (match-string 2) + (re-search-forward + (concat " " (regexp-quote (match-string 2)) " ends here") nil t)) + (progn (setq end (match-end 0)) + (forward-line -1) + (save-excursion + (when (setq new-body (org-babel-tangle-jump-to-org)) + (org-babel-update-block-body new-body))) + (setq counter (+ 1 counter))) + (setq end (point))) (goto-char end)) (prog1 counter (message "Detangled %d code blocks" counter))))) @@ -541,7 +548,8 @@ which enable the original code blocks to be found." (save-match-data (re-search-forward (concat " " (regexp-quote block-name) - " ends here") nil t) + " ends here") + nil t) (setq end (line-beginning-position)))))))) (unless (and start (< start mid) (< mid end)) (error "Not in tangled code")) diff --git a/lisp/org/ob-vala.el b/lisp/org/ob-vala.el index e9c214f7dfc..b1c22756226 100644 --- a/lisp/org/ob-vala.el +++ b/lisp/org/ob-vala.el @@ -26,7 +26,7 @@ ;;; Commentary: ;; ob-vala.el provides Babel support for the Vala language -;; (see http://live.gnome.org/Vala for details) +;; (see https://live.gnome.org/Vala for details) ;;; Requirements: diff --git a/lisp/org/ol-bbdb.el b/lisp/org/ol-bbdb.el index 9f522ce5bdc..73627b901fa 100644 --- a/lisp/org/ol-bbdb.el +++ b/lisp/org/ol-bbdb.el @@ -98,7 +98,7 @@ (require 'org-macs) (require 'ol) -;; Declare functions and variables +;;; Declare functions and variables (declare-function bbdb "ext:bbdb-com" (string elidep)) (declare-function bbdb-company "ext:bbdb-com" (string elidep)) @@ -126,9 +126,9 @@ (declare-function diary-ordinal-suffix "diary-lib" (n)) -(with-no-warnings (defvar date)) ;unprefixed, from calendar.el +(with-no-warnings (defvar date)) ; unprefixed, from calendar.el -;; Customization +;;; Customization (defgroup org-bbdb-anniversaries nil "Customizations for including anniversaries from BBDB into Agenda." @@ -162,13 +162,13 @@ used." '(("birthday" . (lambda (name years suffix) (concat "Birthday: [[bbdb:" name "][" name " (" - (format "%s" years) ; handles numbers as well as strings - suffix ")]]"))) + (format "%s" years) ; handles numbers as well as strings + suffix ")]]"))) ("wedding" . (lambda (name years suffix) (concat "[[bbdb:" name "][" name "'s " - (format "%s" years) - suffix " wedding anniversary]]")))) + (format "%s" years) + suffix " wedding anniversary]]")))) "How different types of anniversaries should be formatted. An alist of elements (STRING . FORMAT) where STRING is the name of an anniversary class and format is either: @@ -221,7 +221,8 @@ date year)." :complete #'org-bbdb-complete-link :store #'org-bbdb-store-link) -;; Implementation +;;; Implementation + (defun org-bbdb-store-link () "Store a link to a BBDB database entry." (when (eq major-mode 'bbdb-mode) @@ -236,7 +237,7 @@ date year)." :link link :description name) link))) -(defun org-bbdb-export (path desc format) +(defun org-bbdb-export (path desc format _) "Create the export version of a BBDB link specified by PATH or DESC. If exporting to either HTML or LaTeX FORMAT the link will be italicized, in all other cases it is left unchanged." @@ -249,7 +250,7 @@ italicized, in all other cases it is left unchanged." (format "%s" desc)) (t desc))) -(defun org-bbdb-open (name) +(defun org-bbdb-open (name _) "Follow a BBDB link to NAME." (require 'bbdb-com) (let ((inhibit-redisplay (not debug-on-error))) @@ -362,7 +363,9 @@ This is used by Org to re-create the anniversary hash table." ;;;###autoload (defun org-bbdb-anniversaries () - "Extract anniversaries from BBDB for display in the agenda." + "Extract anniversaries from BBDB for display in the agenda. +When called programmatically, this function expects the `date' +variable to be globally bound." (require 'bbdb) (require 'diary-lib) (unless (hash-table-p org-bbdb-anniv-hash) @@ -380,7 +383,7 @@ This is used by Org to re-create the anniversary hash table." (text ()) rec recs) - ;; we don't want to miss people born on Feb. 29th + ;; We don't want to miss people born on Feb. 29th (when (and (= m 3) (= d 1) (not (null (gethash (list 2 29) org-bbdb-anniv-hash))) (not (calendar-leap-year-p y))) @@ -415,8 +418,9 @@ This is used by Org to re-create the anniversary hash table." )) text)) -;;; Return list of anniversaries for today and the next n-1 (default: n=7) days. -;;; This is meant to be used in an org file instead of org-bbdb-anniversaries: +;;; Return the list of anniversaries for today and the next n-1 +;;; (default: n=7) days. This is meant to be used in an org file +;;; instead of org-bbdb-anniversaries: ;;; ;;; %%(org-bbdb-anniversaries-future) ;;; @@ -442,15 +446,14 @@ for the same event depending on if it occurs in the next few days or far away in the future." (let ((delta (- (calendar-absolute-from-gregorian anniv-date) (calendar-absolute-from-gregorian agenda-date)))) - (cond ((= delta 0) " -- today\\&") ((= delta 1) " -- tomorrow\\&") - ((< delta org-bbdb-general-anniversary-description-after) (format " -- in %d days\\&" delta)) + ((< delta org-bbdb-general-anniversary-description-after) + (format " -- in %d days\\&" delta)) ((pcase-let ((`(,month ,day ,year) anniv-date)) (format " -- %d-%02d-%02d\\&" year month day)))))) - (defun org-bbdb-anniversaries-future (&optional n) "Return list of anniversaries for today and the next n-1 days (default n=7)." (let ((n (or n 7))) diff --git a/lisp/org/ol-bibtex.el b/lisp/org/ol-bibtex.el index f139d645dad..e8f246e7f64 100644 --- a/lisp/org/ol-bibtex.el +++ b/lisp/org/ol-bibtex.el @@ -95,7 +95,7 @@ ;; The link creation part has been part of Org for a long time. ;; ;; Creating better capture template information was inspired by a request -;; of Austin Frank: http://article.gmane.org/gmane.emacs.orgmode/4112 +;; of Austin Frank: https://orgmode.org/list/m0myu03vbx.fsf@gmail.com ;; and then implemented by Bastien Guerry. ;; ;; Eric Schulte eventually added the functions for translating between @@ -134,7 +134,6 @@ (declare-function org-insert-heading "org" (&optional arg invisible-ok top)) (declare-function org-map-entries "org" (func &optional match scope &rest skip)) (declare-function org-narrow-to-subtree "org" ()) -(declare-function org-open-file "org" (path &optional in-emacs line search)) (declare-function org-set-property "org" (property value)) (declare-function org-toggle-tag "org" (tag &optional onoff)) @@ -483,12 +482,11 @@ With optional argument OPTIONAL, also prompt for optional fields." :follow #'org-bibtex-open :store #'org-bibtex-store-link) -(defun org-bibtex-open (path) - "Visit the bibliography entry on PATH." - (let* ((search (when (string-match "::\\(.+\\)\\'" path) - (match-string 1 path))) - (path (substring path 0 (match-beginning 0)))) - (org-open-file path t nil search))) +(defun org-bibtex-open (path arg) + "Visit the bibliography entry on PATH. +ARG, when non-nil, is a universal prefix argument. See +`org-open-file' for details." + (org-link-open-as-file path arg)) (defun org-bibtex-store-link () "Store a link to a BibTeX entry." @@ -556,7 +554,8 @@ With optional argument OPTIONAL, also prompt for optional fields." ;; We construct a regexp that searches for "@entrytype{" followed by the key (goto-char (point-min)) (and (re-search-forward (concat "@[a-zA-Z]+[ \t\n]*{[ \t\n]*" - (regexp-quote s) "[ \t\n]*,") nil t) + (regexp-quote s) "[ \t\n]*,") + nil t) (goto-char (match-beginning 0))) (if (and (match-beginning 0) (equal current-prefix-arg '(16))) ;; Use double prefix to indicate that any web link should be browsed @@ -596,7 +595,8 @@ Headlines are exported using `org-bibtex-headline'." (with-temp-file filename (insert (mapconcat #'identity bibtex-entries "\n"))) (message "Successfully exported %d BibTeX entries to %s" - (length bibtex-entries) filename) nil)))) + (length bibtex-entries) filename) + nil)))) (when error-point (goto-char error-point) (message "Bibtex error at %S" (nth 4 (org-heading-components)))))) @@ -661,7 +661,8 @@ This uses `bibtex-parse-entry'." (when (and (> (length str) 1) (= (aref str 0) (car pair)) (= (aref str (1- (length str))) (cdr pair))) - (setf str (substring str 1 (1- (length str)))))) str))) + (setf str (substring str 1 (1- (length str)))))) + str))) (push (mapcar (lambda (pair) (cons (let ((field (funcall keyword (car pair)))) diff --git a/lisp/org/ol-docview.el b/lisp/org/ol-docview.el index 22b630299bf..0c6419fbab9 100644 --- a/lisp/org/ol-docview.el +++ b/lisp/org/ol-docview.el @@ -68,7 +68,7 @@ ((eq format 'ascii) (format "%s (%s)" desc path)) (t path))))) -(defun org-docview-open (link) +(defun org-docview-open (link _) (string-match "\\(.*?\\)\\(?:::\\([0-9]+\\)\\)?$" link) (let ((path (match-string 1 link)) (page (and (match-beginning 2) @@ -98,7 +98,6 @@ and append it." "::" (read-from-minibuffer "Page:" "1"))) - (provide 'ol-docview) ;;; ol-docview.el ends here diff --git a/lisp/org/ol-eshell.el b/lisp/org/ol-eshell.el index 7e742f8892a..2bc1a2938ff 100644 --- a/lisp/org/ol-eshell.el +++ b/lisp/org/ol-eshell.el @@ -33,7 +33,7 @@ :follow #'org-eshell-open :store #'org-eshell-store-link) -(defun org-eshell-open (link) +(defun org-eshell-open (link _) "Switch to an eshell buffer and execute a command line. The link can be just a command line (executed in the default eshell buffer) or a command line prefixed by a buffer name diff --git a/lisp/org/ol-eww.el b/lisp/org/ol-eww.el index f32c06b6c89..27e32bc3a3b 100644 --- a/lisp/org/ol-eww.el +++ b/lisp/org/ol-eww.el @@ -46,17 +46,22 @@ ;;; Code: (require 'ol) (require 'cl-lib) +(require 'eww) +;; For Emacsen < 25. (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) +(org-link-set-parameters "eww" + :follow #'org-eww-open + :store #'org-eww-store-link) + +(defun org-eww-open (url _) + "Open URL with Eww in the current buffer." + (eww url)) + (defun org-eww-store-link () "Store a link to the url of an EWW buffer." (when (eq major-mode 'eww-mode) diff --git a/lisp/org/ol-gnus.el b/lisp/org/ol-gnus.el index 99472315f67..af88c1a1532 100644 --- a/lisp/org/ol-gnus.el +++ b/lisp/org/ol-gnus.el @@ -34,7 +34,8 @@ (require 'gnus-sum) (require 'gnus-util) (require 'nnheader) -(require 'nnir) +(or (require 'nnselect nil t) ; Emacs >= 28 + (require 'nnir nil t)) ; Emacs < 28 (require 'ol) @@ -61,7 +62,7 @@ ;;; Customization variables (defcustom org-gnus-prefer-web-links nil - "If non-nil, `org-store-link' creates web links to Google groups or Gmane. + "If non-nil, `org-store-link' creates web links to Google groups. \\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." @@ -87,8 +88,8 @@ negates this setting for the duration of the command." (defun org-gnus-group-link (group) "Create a link to the Gnus group GROUP. If GROUP is a newsgroup and `org-gnus-prefer-web-links' is -non-nil, create a link to groups.google.com or gmane.org. -Otherwise create a link to the group inside Gnus. +non-nil, create a link to groups.google.com. 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." @@ -96,10 +97,7 @@ If `org-store-link' was called with a prefix arg the meaning of (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) - "http://news.gmane.org/" - "http://groups.google.com/group/") - unprefixed-group) + (concat "https://groups.google.com/group/" unprefixed-group) (concat "gnus:" group)))) (defun org-gnus-article-link (group newsgroups message-id x-no-archive) @@ -110,7 +108,7 @@ parameters are the Gnus GROUP, the NEWSGROUPS the article was posted to and the X-NO-ARCHIVE header value of that article. If GROUP is a newsgroup and `org-gnus-prefer-web-links' is -non-nil, create a link to groups.google.com or gmane.org. +non-nil, create a link to groups.google.com. Otherwise create a link to the article inside Gnus. If `org-store-link' was called with a prefix arg the meaning of @@ -118,9 +116,7 @@ If `org-store-link' was called with a prefix arg the meaning of (if (and (org-xor current-prefix-arg org-gnus-prefer-web-links) newsgroups ;make web links only for nntp groups (not x-no-archive)) ;and if X-No-Archive isn't set - (format (if (string-match-p "gmane\\." newsgroups) - "http://mid.gmane.org/%s" - "http://groups.google.com/groups/search?as_umsgid=%s") + (format "https://groups.google.com/groups/search?as_umsgid=%s" (url-encode-url message-id)) (concat "gnus:" group "#" message-id))) @@ -140,9 +136,15 @@ If `org-store-link' was called with a prefix arg the meaning of (`(nnvirtual . ,_) (save-excursion (car (nnvirtual-map-article (gnus-summary-article-number))))) - (`(nnir . ,_) + (`(,(or `nnselect `nnir) . ,_) ; nnir is for Emacs < 28. (save-excursion - (nnir-article-group (gnus-summary-article-number)))) + (cond + ((fboundp 'nnselect-article-group) + (nnselect-article-group (gnus-summary-article-number))) + ((fboundp 'nnir-article-group) + (nnir-article-group (gnus-summary-article-number))) + (t + (error "No article-group variant bound"))))) (_ gnus-newsgroup-name))) (header (if (eq major-mode 'gnus-article-mode) ;; When in an article, first move to summary @@ -215,7 +217,7 @@ If `org-store-link' was called with a prefix arg the meaning of (format "nntp+%s:%s" (or (cdr server) (car server)) group) article))) -(defun org-gnus-open (path) +(defun org-gnus-open (path _) "Follow the Gnus message or folder link specified by PATH." (unless (string-match "\\`\\([^#]+\\)\\(#\\(.*\\)\\)?" path) (error "Error in Gnus link %S" path)) diff --git a/lisp/org/ol-info.el b/lisp/org/ol-info.el index 58d45a7f7ee..864fbc47de3 100644 --- a/lisp/org/ol-info.el +++ b/lisp/org/ol-info.el @@ -59,7 +59,7 @@ :link link :desc desc) link))) -(defun org-info-open (path) +(defun org-info-open (path _) "Follow an Info file and node link specified by PATH." (org-info-follow-link path)) diff --git a/lisp/org/ol-irc.el b/lisp/org/ol-irc.el index 3a347791eec..a2f8086b313 100644 --- a/lisp/org/ol-irc.el +++ b/lisp/org/ol-irc.el @@ -78,7 +78,7 @@ :store #'org-irc-store-link :export #'org-irc-export) -(defun org-irc-visit (link) +(defun org-irc-visit (link _) "Parse LINK and dispatch to the correct function based on the client found." (let ((link (org-irc-parse-link link))) (cond diff --git a/lisp/org/ol-mhe.el b/lisp/org/ol-mhe.el index 099882db1c5..50002b0e872 100644 --- a/lisp/org/ol-mhe.el +++ b/lisp/org/ol-mhe.el @@ -96,7 +96,7 @@ supported by MH-E." (org-link-add-props :link link :description desc) link)))) -(defun org-mhe-open (path) +(defun org-mhe-open (path _) "Follow an MH-E message link specified by PATH." (let (folder article) (if (not (string-match "\\`\\([^#]+\\)\\(#\\(.*\\)\\)?" path)) diff --git a/lisp/org/ol-rmail.el b/lisp/org/ol-rmail.el index cad8eaa169a..e43fc932ee2 100644 --- a/lisp/org/ol-rmail.el +++ b/lisp/org/ol-rmail.el @@ -43,7 +43,9 @@ (defvar rmail-file-name) ; From rmail.el ;; Install the link type -(org-link-set-parameters "rmail" :follow #'org-rmail-open :store #'org-rmail-store-link) +(org-link-set-parameters "rmail" + :follow #'org-rmail-open + :store #'org-rmail-store-link) ;; Implementation (defun org-rmail-store-link () @@ -75,7 +77,7 @@ (rmail-show-message rmail-current-message) link))))) -(defun org-rmail-open (path) +(defun org-rmail-open (path _) "Follow an Rmail message link to the specified PATH." (let (folder article) (if (not (string-match "\\`\\([^#]+\\)\\(#\\(.*\\)\\)?" path)) diff --git a/lisp/org/ol.el b/lisp/org/ol.el index baed23bc9a4..77ca21e2643 100644 --- a/lisp/org/ol.el +++ b/lisp/org/ol.el @@ -45,6 +45,7 @@ (declare-function dired-get-filename "dired" (&optional localp no-error-if-not-filep)) (declare-function org-at-heading-p "org" (&optional _)) (declare-function org-back-to-heading "org" (&optional invisible-ok)) +(declare-function org-before-first-heading-p "org" ()) (declare-function org-do-occur "org" (regexp &optional cleanup)) (declare-function org-element-at-point "org-element" ()) (declare-function org-element-cache-refresh "org-element" (pos)) @@ -57,7 +58,6 @@ (declare-function org-entry-get "org" (pom property &optional inherit literal-nil)) (declare-function org-find-property "org" (property &optional value)) (declare-function org-get-heading "org" (&optional no-tags no-todo no-priority no-comment)) -(declare-function org-heading-components "org" ()) (declare-function org-id-find-id-file "org-id" (id)) (declare-function org-id-store-link "org-id" ()) (declare-function org-insert-heading "org" (&optional arg invisible-ok top)) @@ -85,42 +85,94 @@ :group 'org) (defcustom org-link-parameters nil - "An alist of properties that defines all the links in Org mode. + "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 plist of link properties. +Subsequent optional elements make up a property list for that +type. + +All properties are optional. However, the most important ones +are, in this order, `:follow', `:export', and `:store', described +below. + +`:follow' + + Function used to follow the link, when the `org-open-at-point' + command runs on it. It is called with two arguments: the path, + as a string, and a universal prefix argument. + + Here, you may use `org-link-open-as-file' helper function for + types similar to \"file\". + +`:export' + + Function that accepts four arguments: + - the path, as a string, + - the description as a string, or nil, + - the export back-end, + - the export communication channel, as a plist. + + When nil, export for that type of link is delegated to the + back-end. + +`:store' + + Function responsible for storing the link. See the function + `org-store-link-functions' for a description of the expected + arguments. + +Additional properties provide more specific control over the +link. + +`:activate-func' + + Function to run at the end of Font Lock activation. It must + accept four arguments: + - the buffer position at the start of the link, + - the buffer position at its end, + - the path, as a string, + - a boolean, non-nil when the link has brackets. -:follow - A function that takes the link path as an argument. +`:complete' -:export - A function that takes the link path, description and -export-backend as arguments. + Function that inserts a link with completion. The function + takes one optional prefix argument. -:store - A function responsible for storing the link. See the -function `org-store-link-functions'. +`:display' -:complete - A function that inserts a link with completion. The -function takes one optional prefix argument. + Value for `invisible' text property on the hidden parts of the + link. The most useful value is `full', which will not fold the + link in descriptive display. Default is `org-link'. -: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'. +`:face' -:mouse-face - The mouse-face. The default is `highlight'. + Face for the link, or a function returning a face. The + function takes one argument, which is the path. -:display - `full' will not fold the link in descriptive -display. Default is `org-link'. + The default face is `org-link'. -:help-echo - A string or function that takes (window object position) -as arguments and returns a string. +`:help-echo' -:keymap - A keymap that is active on the link. The default is -`org-mouse-map'. + String or function used as a value for the `help-echo' text + property. The function is called with one argument, the help + string to display, and should return a string. -:htmlize-link - A function for the htmlize-link. Defaults -to (list :uri \"type:path\") +`:htmlize-link' -: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." + Function or plist for the `htmlize-link' text property. The + function takes no argument. + + Default is (:uri \"type:path\") + +`:keymap' + + Active keymap when point is on the link. Default is + `org-mouse-map'. + +`:mouse-face' + + Face used when hovering over the link. Default is + `highlight'." :group 'org-link :package-version '(Org . "9.1") :type '(alist :tag "Link display parameters" @@ -408,7 +460,7 @@ This is for example useful to limit the length of the subject. Examples: \"%f on: %.30s\", \"Email from %f\", \"Email %c\"" :group 'org-link-store - :package-version '(Org . 9.3) + :package-version '(Org . "9.3") :type 'string :safe #'stringp) @@ -674,6 +726,44 @@ White spaces are not significant." (goto-char origin) (user-error "No match for radio target: %s" target)))) +(defun org-link--context-from-region () + "Return context string from active region, or nil." + (when (org-region-active-p) + (let ((context (buffer-substring (region-beginning) (region-end)))) + (when (and (wholenump org-link-context-for-files) + (> org-link-context-for-files 0)) + (let ((lines (org-split-string context "\n"))) + (setq context + (mapconcat #'identity + (cl-subseq lines 0 org-link-context-for-files) + "\n")))) + context))) + +(defun org-link--normalize-string (string &optional context) + "Remove ignored contents from STRING string and return it. +This function removes contiguous white spaces and statistics +cookies. When optional argument CONTEXT is non-nil, it assumes +STRING is a context string, and also removes special search +syntax around the string." + (let ((string + (org-trim + (replace-regexp-in-string + (rx (one-or-more (any " \t"))) + " " + (replace-regexp-in-string + ;; Statistics cookie regexp. + (rx (seq "[" (0+ digit) (or "%" (seq "/" (0+ digit))) "]")) + " " + string))))) + (when context + (while (cond ((and (string-prefix-p "(" string) + (string-suffix-p ")" string)) + (setq string (org-trim (substring string 1 -1)))) + ((string-match "\\`[#*]+[ \t]*" string) + (setq string (substring string (match-end 0)))) + (t nil)))) + string)) + ;;; Public API @@ -692,6 +782,8 @@ TYPE is a string and KEY is a plist keyword. See "Set link TYPE properties to PARAMETERS. PARAMETERS should be keyword value pairs. See `org-link-parameters' for supported keys." + (when (member type '("coderef" "custom-id" "fuzzy" "radio")) + (error "Cannot override reserved link type: %S" type)) (let ((data (assoc type org-link-parameters))) (if data (setcdr data (org-combine-plists (cdr data) parameters)) (push (cons type parameters) org-link-parameters) @@ -716,12 +808,10 @@ This should be called after the variable `org-link-parameters' has changed." (rx (seq "[[" ;; URI part: match group 1. (group - ;; Allow an even number of backslashes right - ;; before the closing bracket. - (or (one-or-more "\\\\") - (and (*? anything) - (not (any "\\")) - (zero-or-more "\\\\")))) + (one-or-more + (or (not (any "[]\\")) + (and "\\" (zero-or-more "\\\\") (any "[]")) + (and (one-or-more "\\") (not (any "[]")))))) "]" ;; Description (optional): match group 2. (opt "[" (group (+? anything)) "]") @@ -838,37 +928,26 @@ E.g. \"%C3%B6\" becomes the german o-Umlaut." (defun org-link-escape (link) "Backslash-escape sensitive characters in string LINK." - ;; Escape closing square brackets followed by another square bracket - ;; or at the end of the link. Also escape final backslashes so that - ;; we do not escape inadvertently URI's closing bracket. - (with-temp-buffer - (insert link) - (insert (make-string (- (skip-chars-backward "\\\\")) - ?\\)) - (while (search-backward "\]" nil t) - (when (looking-at-p "\\]\\(?:[][]\\|\\'\\)") - (insert (make-string (1+ (- (skip-chars-backward "\\\\"))) - ?\\)))) - (buffer-string))) + (replace-regexp-in-string + (rx (seq (group (zero-or-more "\\")) (group (or string-end (any "[]"))))) + (lambda (m) + (concat (match-string 1 m) + (match-string 1 m) + (and (/= (match-beginning 2) (match-end 2)) "\\"))) + link nil t 1)) (defun org-link-unescape (link) "Remove escaping backslash characters from string LINK." - (with-temp-buffer - (save-excursion (insert link)) - (while (re-search-forward "\\(\\\\+\\)\\]\\(?:[][]\\|\\'\\)" nil t) - (replace-match (make-string (/ (- (match-end 1) (match-beginning 1)) 2) - ?\\) - nil t nil 1)) - (goto-char (point-max)) - (delete-char (/ (- (skip-chars-backward "\\\\")) 2)) - (buffer-string))) + (replace-regexp-in-string + (rx (group (one-or-more "\\")) (or string-end (any "[]"))) + (lambda (_) + (concat (make-string (/ (- (match-end 1) (match-beginning 1)) 2) ?\\))) + link nil t 1)) (defun org-link-make-string (link &optional description) "Make a bracket link, consisting of LINK and DESCRIPTION. LINK is escaped with backslashes for inclusion in buffer." - (unless (org-string-nw-p link) (error "Empty link")) - (let* ((uri (org-link-escape link)) - (zero-width-space (string ?\x200B)) + (let* ((zero-width-space (string ?\x200B)) (description (and (org-string-nw-p description) ;; Description cannot contain two consecutive square @@ -881,9 +960,10 @@ LINK is escaped with backslashes for inclusion in buffer." (replace-regexp-in-string "]\\'" (concat "\\&" zero-width-space) (org-trim description)))))) - (format "[[%s]%s]" - uri - (if description (format "[%s]" description) "")))) + (if (not (org-string-nw-p link)) description + (format "[[%s]%s]" + (org-link-escape link) + (if description (format "[%s]" description) ""))))) (defun org-store-link-functions () "List of functions that are called to create and store a link. @@ -930,7 +1010,8 @@ Abbreviations are defined in `org-link-abbrev-alist'." ((string-match "%(\\([^)]+\\))" rpl) (replace-match (save-match-data - (funcall (intern-soft (match-string 1 rpl)) tag)) t t rpl)) + (funcall (intern-soft (match-string 1 rpl)) tag)) + t t rpl)) ((string-match "%s" rpl) (replace-match (or tag "") t t rpl)) ((string-match "%h" rpl) (replace-match (url-hexify-string (or tag "")) t t rpl)) @@ -938,63 +1019,60 @@ Abbreviations are defined in `org-link-abbrev-alist'." (defun org-link-open (link &optional arg) "Open a link object LINK. -Optional argument is passed to `org-open-file' when S is -a \"file\" link." + +ARG is an optional prefix argument. Some link types may handle +it. For example, it determines what application to run when +opening a \"file\" link. + +Functions responsible for opening the link are either hard-coded +for internal and \"file\" links, or stored as a parameter in +`org-link-parameters', which see." (let ((type (org-element-property :type link)) (path (org-element-property :path link))) - (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 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-link--buffer-for-internals))) - (let ((destination - (org-with-wide-buffer - (if (equal type "radio") - (org-link--search-radio-target - (org-element-property :path link)) - (org-link-search - (pcase type - ("custom-id" (concat "#" path)) - ("coderef" (format "(%s)" path)) - (_ 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))))) + (pcase type + ;; Opening a "file" link requires special treatment since we + ;; first need to integrate search option, if any. + ("file" + (let* ((option (org-element-property :search-option link)) + (path (if option (concat path "::" option) path))) + (org-link-open-as-file path + (pcase (org-element-property :application link) + ((guard arg) arg) + ("emacs" 'emacs) + ("sys" 'system))))) + ;; Internal links. + ((or "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-link--buffer-for-internals))) + (let ((destination + (org-with-wide-buffer + (if (equal type "radio") + (org-link--search-radio-target path) + (org-link-search + (pcase type + ("custom-id" (concat "#" path)) + ("coderef" (format "(%s)" path)) + (_ 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)))) + (_ + ;; Look for a dedicated "follow" function in custom links. + (let ((f (org-link-get-parameter type :follow))) + (when (functionp f) + ;; Function defined in `:follow' parameter may use a single + ;; argument, as it was mandatory before Org 9.4. This is + ;; deprecated, but support it for now. + (condition-case nil + (funcall (org-link-get-parameter type :follow) path arg) + (wrong-number-of-arguments + (funcall (org-link-get-parameter type :follow) path))))))))) (defun org-link-open-from-string (s &optional arg) "Open a link in the string S, as if it was in Org mode. @@ -1095,10 +1173,9 @@ of matched result, which is either `dedicated' or `fuzzy'." (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))) + (let* ((element (org-element-at-point)) + (name (org-element-property :name element))) + (when (and name (equal words (split-string name))) (setq type 'dedicated) (beginning-of-line) (throw :name-match t)))) @@ -1111,18 +1188,14 @@ of matched result, which is either `dedicated' or `fuzzy'." (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))) + (mapconcat #'regexp-quote words ".+")))) (goto-char (point-min)) (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 t))))) + (org-link--normalize-string + (org-get-heading t t t t)))) (throw :found t))) nil))) (beginning-of-line) @@ -1173,24 +1246,40 @@ of matched result, which is either `dedicated' or `fuzzy'." type)) (defun org-link-heading-search-string (&optional string) - "Make search string for the current headline or STRING." - (let ((s (or string - (and (derived-mode-p 'org-mode) - (save-excursion - (org-back-to-heading t) - (org-element-property :raw-value - (org-element-at-point)))))) - (lines org-link-context-for-files)) - (unless string (setq s (concat "*" s))) ;Add * for headlines - (setq s (replace-regexp-in-string "\\[[0-9]+%\\]\\|\\[[0-9]+/[0-9]+\\]" "" s)) - (when (and string (integerp lines) (> lines 0)) - (let ((slines (org-split-string s "\n"))) - (when (< lines (length slines)) - (setq s (mapconcat - #'identity - (reverse (nthcdr (- (length slines) lines) - (reverse slines))) "\n"))))) - (mapconcat #'identity (split-string s) " "))) + "Make search string for the current headline or STRING. + +Search string starts with an asterisk. COMMENT keyword and +statistics cookies are removed, and contiguous spaces are packed +into a single one. + +When optional argument STRING is non-nil, assume it a headline, +without any asterisk, TODO or COMMENT keyword, and without any +priority cookie or tag." + (concat "*" + (org-link--normalize-string + (or string (org-get-heading t t t t))))) + +(defun org-link-open-as-file (path arg) + "Pretend PATH is a file name and open it. + +According to \"file\"-link syntax, PATH may include additional +search options, separated from the file name with \"::\". + +This function is meant to be used as a possible tool for +`:follow' property in `org-link-parameters'." + (let* ((option (and (string-match "::\\(.*\\)\\'" path) + (match-string 1 path))) + (file-name (if (not option) path + (substring path 0 (match-beginning 0))))) + (if (string-match "[*?{]" (file-name-nondirectory file-name)) + (dired file-name) + (apply #'org-open-file + file-name + arg + (cond ((not option) nil) + ((string-match-p "\\`[0-9]+\\'" option) + (list (string-to-number option))) + (t (list nil option))))))) (defun org-link-display-format (s) "Replace links in string S with their description. @@ -1211,15 +1300,15 @@ If there is no description, use the link target." ;;; Built-in link types ;;;; "doi" link type -(defun org-link--open-doi (path) +(defun org-link--open-doi (path arg) "Open a \"doi\" type link. PATH is a the path to search for, as a string." - (browse-url (url-encode-url (concat org-link-doi-server-url path)))) + (browse-url (url-encode-url (concat org-link-doi-server-url path)) arg)) (org-link-set-parameters "doi" :follow #'org-link--open-doi) ;;;; "elisp" link type -(defun org-link--open-elisp (path) +(defun org-link--open-elisp (path _) "Open a \"elisp\" type link. PATH is the sexp to evaluate, as a string." (if (or (and (org-string-nw-p org-link-elisp-skip-confirm-regexp) @@ -1240,7 +1329,7 @@ PATH is the sexp to evaluate, as a string." (org-link-set-parameters "file" :complete #'org-link-complete-file) ;;;; "help" link type -(defun org-link--open-help (path) +(defun org-link--open-help (path _) "Open a \"help\" type link. PATH is a symbol name, as a string." (pcase (intern path) @@ -1254,10 +1343,11 @@ PATH is a symbol name, as a string." (dolist (scheme '("ftp" "http" "https" "mailto" "news")) (org-link-set-parameters scheme :follow - (lambda (url) (browse-url (concat scheme ":" url))))) + (lambda (url arg) + (browse-url (concat scheme ":" url) arg)))) ;;;; "shell" link type -(defun org-link--open-shell (path) +(defun org-link--open-shell (path _) "Open a \"shell\" type link. PATH is the command to execute, as a string." (if (or (and (org-string-nw-p org-link-shell-skip-confirm-regexp) @@ -1375,7 +1465,7 @@ non-nil." (move-beginning-of-line 2) (set-mark (point))))) (setq org-store-link-plist nil) - (let (link cpltxt desc description search txt custom-id agenda-link) + (let (link cpltxt desc description search custom-id agenda-link) (cond ;; Store a link using an external link type, if any function is ;; available. If more than one can generate a link from current @@ -1465,10 +1555,16 @@ non-nil." (org-link-store-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)))) + (let ((symbol (replace-regexp-in-string + ;; Help mode escapes backquotes and backslashes + ;; before displaying them. E.g., "`" appears + ;; as "\'" for reasons. Work around this. + (rx "\\" (group (or "`" "\\"))) "\\1" + (save-excursion + (goto-char (point-min)) + (looking-at "^[^ ]+") + (match-string 0))))) + (setq link (concat "help:" symbol))) (org-link-store-props :type "help")) ((eq major-mode 'w3-mode) @@ -1534,30 +1630,35 @@ non-nil." (abbreviate-file-name (buffer-file-name (buffer-base-buffer)))))))) (t - ;; Just link to current headline + ;; Just link to current headline. (setq cpltxt (concat "file:" (abbreviate-file-name (buffer-file-name (buffer-base-buffer))))) - ;; Add a context search string + ;; Add a context search string. (when (org-xor org-link-context-for-files (equal arg '(4))) (let* ((element (org-element-at-point)) - (name (org-element-property :name element))) - (setq txt (cond - ((org-at-heading-p) nil) - (name) - ((org-region-active-p) - (buffer-substring (region-beginning) (region-end))))) - (when (or (null txt) (string-match "\\S-" txt)) - (setq cpltxt - (concat cpltxt "::" - (condition-case nil - (org-link-heading-search-string txt) - (error ""))) - desc (or name - (nth 4 (ignore-errors (org-heading-components))) - "NONE"))))) - (when (string-match "::\\'" cpltxt) - (setq cpltxt (substring cpltxt 0 -2))) + (name (org-element-property :name element)) + (context + (cond + ((let ((region (org-link--context-from-region))) + (and region (org-link--normalize-string region t)))) + (name) + ((org-before-first-heading-p) + (org-link--normalize-string (org-current-line-string) t)) + (t (org-link-heading-search-string))))) + (when (org-string-nw-p context) + (setq cpltxt (format "%s::%s" cpltxt context)) + (setq desc + (or name + ;; Although description is not a search + ;; string, use `org-link--normalize-string' + ;; to prettify it (contiguous white spaces) + ;; and remove volatile contents (statistics + ;; cookies). + (and (not (org-before-first-heading-p)) + (org-link--normalize-string + (org-get-heading t t t t))) + "NONE"))))) (setq link cpltxt))))) ((buffer-file-name (buffer-base-buffer)) @@ -1565,16 +1666,16 @@ non-nil." (setq cpltxt (concat "file:" (abbreviate-file-name (buffer-file-name (buffer-base-buffer))))) - ;; Add a context string. + ;; Add a context search string. (when (org-xor org-link-context-for-files (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-link-heading-search-string txt)) - desc "NONE"))) + (let ((context (org-link--normalize-string + (or (org-link--context-from-region) + (org-current-line-string)) + t))) + ;; Only use search option if there is some text. + (when (org-string-nw-p context) + (setq cpltxt (format "%s::%s" cpltxt context)) + (setq desc "NONE")))) (setq link cpltxt)) (interactive? @@ -1589,15 +1690,19 @@ non-nil." (cond ((not desc)) ((equal desc "NONE") (setq desc nil)) (t (setq desc (org-link-display-format desc)))) - ;; Return the link + ;; Store and return the link (if (not (and interactive? link)) (or agenda-link (and link (org-link-make-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)) + (if (member (list link desc) org-stored-links) + (message "This link already exists") + (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 (buffer-base-buffer))) + "::#" custom-id)) + (push (list link desc) org-stored-links))) (car org-stored-links))))) ;;;###autoload @@ -1737,13 +1842,14 @@ Use TAB to complete link prefixes, then RET for type-specific completion support ;; 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 + (when (and (buffer-file-name (buffer-base-buffer)) (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 - (when (equal (file-truename buffer-file-name) (file-truename path)) + (when (equal (file-truename (buffer-file-name (buffer-base-buffer))) + (file-truename path)) ;; We are linking to this same file, with a search option (setq link search))))) @@ -1903,7 +2009,10 @@ Also refresh fontification if needed." (org-link-make-regexps) - (provide 'ol) +;; Local variables: +;; generated-autoload-file: "org-loaddefs.el" +;; End: + ;;; ol.el ends here diff --git a/lisp/org/org-agenda.el b/lisp/org/org-agenda.el index 4f89ea54500..83f30bf96af 100644 --- a/lisp/org/org-agenda.el +++ b/lisp/org/org-agenda.el @@ -49,6 +49,7 @@ (require 'ol) (require 'org) (require 'org-macs) +(require 'org-refile) (declare-function diary-add-to-list "diary-lib" (date string specifier &optional marker globcolor literal)) @@ -83,6 +84,7 @@ (declare-function org-agenda-columns "org-colview" ()) (declare-function org-add-archive-files "org-archive" (files)) (declare-function org-capture "org-capture" (&optional goto keys)) +(declare-function org-clock-modify-effort-estimate "org-clock" (&optional value)) (defvar calendar-mode-map) (defvar org-clock-current-task) @@ -185,7 +187,7 @@ and `org-agenda-entry-text-maxlines'." "Non-nil means export org-links as descriptive links in agenda added text. This variable applies to the text added to the agenda when `org-agenda-add-entry-text-maxlines' is larger than 0. -When this variable nil, the URL will (also) be shown." +When this variable is nil, the URL will (also) be shown." :group 'org-agenda :type 'boolean) @@ -1012,6 +1014,12 @@ headlines as the agenda display heavily relies on them." :group 'org-agenda-startup :type 'hook) +(defcustom org-agenda-filter-hook nil + "Hook run just after filtering with `org-agenda-filter'." + :group 'org-agenda-startup + :package-version '(Org . "9.4") + :type 'hook) + (defcustom org-agenda-mouse-1-follows-link nil "Non-nil means mouse-1 on a link will follow the link in the agenda. A longer mouse click will still set point. Needs to be set @@ -1092,14 +1100,21 @@ reorganize-frame Show only two windows on the current frame, the current window and the agenda. other-frame Use `switch-to-buffer-other-frame' to display agenda. Also, when exiting the agenda, kill that frame. +other-tab Use `switch-to-buffer-other-tab' to display the + agenda, making use of the `tab-bar-mode' introduced + in Emacs version 27.1. Also, kill that tab when + exiting the agenda view. + See also the variable `org-agenda-restore-windows-after-quit'." :group 'org-agenda-windows :type '(choice (const current-window) (const other-frame) + (const other-tab) (const other-window) (const only-window) - (const reorganize-frame))) + (const reorganize-frame)) + :package-version '(Org . "9.4")) (defcustom org-agenda-window-frame-fractions '(0.5 . 0.75) "The min and max height of the agenda window as a fraction of frame height. @@ -1110,11 +1125,11 @@ It only matters if `org-agenda-window-setup' is `reorganize-frame'." (defcustom org-agenda-restore-windows-after-quit nil "Non-nil means restore window configuration upon exiting agenda. -Before the window configuration is changed for displaying the agenda, -the current status is recorded. When the agenda is exited with -`q' or `x' and this option is set, the old state is restored. If -`org-agenda-window-setup' is `other-frame', the value of this -option will be ignored." +Before the window configuration is changed for displaying the +agenda, the current status is recorded. When the agenda is +exited with `q' or `x' and this option is set, the old state is +restored. If `org-agenda-window-setup' is `other-frame' or +`other-tab', the value of this option will be ignored." :group 'org-agenda-windows :type 'boolean) @@ -1156,6 +1171,11 @@ argument, a calendar-style date list like (month day year)." (string :tag "Format string") (function :tag "Function"))) +(defun org-agenda-end-of-line () + "Go to the end of visible line." + (interactive) + (goto-char (line-end-position))) + (defun org-agenda-format-date-aligned (date) "Format a DATE string for display in the daily/weekly agenda. This function makes sure that dates are aligned for easy reading." @@ -1238,6 +1258,16 @@ in the past." :version "24.1" :type 'boolean) +(defcustom org-agenda-diary-file 'diary-file + "File to which to add new entries with the `i' key in agenda and calendar. +When this is the symbol `diary-file', the functionality in the Emacs +calendar will be used to add entries to the `diary-file'. But when this +points to a file, `org-agenda-diary-entry' will be used instead." + :group 'org-agenda + :type '(choice + (const :tag "The standard Emacs diary file" diary-file) + (file :tag "Special Org file diary entries"))) + (defcustom org-agenda-include-diary nil "If non-nil, include in the agenda entries from the Emacs Calendar's diary. Custom commands can set this variable in the options section." @@ -1619,7 +1649,7 @@ part of an agenda sorting strategy." :group 'org-agenda-sorting :type 'symbol) -(defcustom org-sort-agenda-notime-is-late t +(defcustom org-agenda-sort-notime-is-late t "Non-nil means items without time are considered late. This is only relevant for sorting. When t, items which have no explicit time like 15:30 will be considered as 99:01, i.e. later than any items which @@ -1629,7 +1659,7 @@ agenda entries." :group 'org-agenda-sorting :type 'boolean) -(defcustom org-sort-agenda-noeffort-is-high t +(defcustom org-agenda-sort-noeffort-is-high t "Non-nil means items without effort estimate are sorted as high effort. This also applies when filtering an agenda view with respect to the < or > effort operator. Then, tasks with no effort defined will be treated @@ -1883,7 +1913,7 @@ Nil means don't hide any tags." :group 'org-agenda-line-format :type '(choice (const :tag "Hide none" nil) - (string :tag "Regexp "))) + (regexp :tag "Regexp "))) (defvaralias 'org-agenda-remove-tags-when-in-prefix 'org-agenda-remove-tags) @@ -1923,8 +1953,8 @@ However, settings in `org-priority-faces' will overrule these faces. When this variable is the symbol `cookies', only fontify the cookies, not the entire task. This may also be an association list of priority faces, whose -keys are the character values of `org-highest-priority', -`org-default-priority', and `org-lowest-priority' (the default values +keys are the character values of `org-priority-highest', +`org-priority-default', and `org-priority-lowest' (the default values are ?A, ?B, and ?C, respectively). The face may be a named face, a color as a string, or a list like `(:background \"Red\")'. If it is a color, the variable `org-faces-easy-properties' @@ -1980,12 +2010,12 @@ category, you can use: (\"Emacs\" \\='(space . (:width (16))))" :group 'org-agenda-line-format :version "24.1" - :type '(alist :key-type (string :tag "Regexp matching category") + :type '(alist :key-type (regexp :tag "Regexp matching category") :value-type (choice (list :tag "Icon" (string :tag "File or data") (symbol :tag "Type") (boolean :tag "Data?") - (repeat :tag "Extra image properties" :inline t symbol)) + (repeat :tag "Extra image properties" :inline t sexp)) (list :tag "Display properties" sexp)))) (defgroup org-agenda-column-view nil @@ -2101,6 +2131,8 @@ evaluate to a string." (defvar org-agenda-mode-map (make-sparse-keymap) "Keymap for `org-agenda-mode'.") +(org-remap org-agenda-mode-map 'move-end-of-line 'org-agenda-end-of-line) + (defvar org-agenda-menu) ; defined later in this file. (defvar org-agenda-restrict nil) ; defined later in this file. (defvar org-agenda-follow-mode nil) @@ -2197,6 +2229,7 @@ The following commands are available: \\{org-agenda-mode-map}" (interactive) + (ignore-errors (require 'face-remap)) (let ((agenda-local-vars-to-keep '(text-scale-mode-amount text-scale-mode @@ -2209,8 +2242,8 @@ The following commands are available: (dolist (elem save) (pcase elem (`(,var . ,val) ;ignore unbound variables - (when (and val (memq var var-set)) - (set var val))))))) + (when (and val (memq var var-set)) + (set var val))))))) (cond (org-agenda-doing-sticky-redo ;; Refreshing sticky agenda-buffer ;; @@ -2236,7 +2269,6 @@ The following commands are available: (setq mode-name "Org-Agenda") (setq indent-tabs-mode nil) (use-local-map org-agenda-mode-map) - (easy-menu-add org-agenda-menu) (when org-startup-truncated (setq truncate-lines t)) (setq-local line-move-visual nil) (add-hook 'post-command-hook 'org-agenda-update-agenda-type nil 'local) @@ -2274,155 +2306,152 @@ The following commands are available: (if (fboundp 'run-mode-hooks) 'run-mode-hooks 'run-hooks) (list 'org-agenda-mode-hook))) -(substitute-key-definition 'undo 'org-agenda-undo +(substitute-key-definition #'undo #'org-agenda-undo org-agenda-mode-map global-map) -(org-defkey org-agenda-mode-map "\C-i" 'org-agenda-goto) -(org-defkey org-agenda-mode-map [(tab)] 'org-agenda-goto) -(org-defkey org-agenda-mode-map "\C-m" 'org-agenda-switch-to) -(org-defkey org-agenda-mode-map "\C-k" 'org-agenda-kill) -(org-defkey org-agenda-mode-map "\C-c\C-w" 'org-agenda-refile) -(org-defkey org-agenda-mode-map [(meta down)] 'org-agenda-drag-line-forward) -(org-defkey org-agenda-mode-map [(meta up)] 'org-agenda-drag-line-backward) -(org-defkey org-agenda-mode-map "m" 'org-agenda-bulk-mark) -(org-defkey org-agenda-mode-map "\M-m" 'org-agenda-bulk-toggle) -(org-defkey org-agenda-mode-map "*" 'org-agenda-bulk-mark-all) -(org-defkey org-agenda-mode-map "\M-*" 'org-agenda-bulk-toggle-all) -(org-defkey org-agenda-mode-map "#" 'org-agenda-dim-blocked-tasks) -(org-defkey org-agenda-mode-map "%" 'org-agenda-bulk-mark-regexp) -(org-defkey org-agenda-mode-map "u" 'org-agenda-bulk-unmark) -(org-defkey org-agenda-mode-map "U" 'org-agenda-bulk-unmark-all) -(org-defkey org-agenda-mode-map "B" 'org-agenda-bulk-action) -(org-defkey org-agenda-mode-map "k" 'org-agenda-capture) -(org-defkey org-agenda-mode-map "A" 'org-agenda-append-agenda) -(org-defkey org-agenda-mode-map "\C-c\C-x!" 'org-reload) -(org-defkey org-agenda-mode-map "\C-c\C-x\C-a" 'org-agenda-archive-default) -(org-defkey org-agenda-mode-map "\C-c\C-xa" 'org-agenda-toggle-archive-tag) -(org-defkey org-agenda-mode-map "\C-c\C-xA" 'org-agenda-archive-to-archive-sibling) -(org-defkey org-agenda-mode-map "\C-c\C-x\C-s" 'org-agenda-archive) -(org-defkey org-agenda-mode-map "\C-c$" 'org-agenda-archive) -(org-defkey org-agenda-mode-map "$" 'org-agenda-archive) -(org-defkey org-agenda-mode-map "\C-c\C-o" 'org-agenda-open-link) -(org-defkey org-agenda-mode-map " " 'org-agenda-show-and-scroll-up) -(org-defkey org-agenda-mode-map [backspace] 'org-agenda-show-scroll-down) -(org-defkey org-agenda-mode-map "\d" 'org-agenda-show-scroll-down) -(org-defkey org-agenda-mode-map [(control shift right)] 'org-agenda-todo-nextset) -(org-defkey org-agenda-mode-map [(control shift left)] 'org-agenda-todo-previousset) -(org-defkey org-agenda-mode-map "\C-c\C-xb" 'org-agenda-tree-to-indirect-buffer) -(org-defkey org-agenda-mode-map "o" 'delete-other-windows) -(org-defkey org-agenda-mode-map "L" 'org-agenda-recenter) -(org-defkey org-agenda-mode-map "\C-c\C-t" 'org-agenda-todo) -(org-defkey org-agenda-mode-map "t" 'org-agenda-todo) -(org-defkey org-agenda-mode-map "a" 'org-agenda-archive-default-with-confirmation) -(org-defkey org-agenda-mode-map ":" 'org-agenda-set-tags) -(org-defkey org-agenda-mode-map "\C-c\C-q" 'org-agenda-set-tags) -(org-defkey org-agenda-mode-map "." 'org-agenda-goto-today) -(org-defkey org-agenda-mode-map "j" 'org-agenda-goto-date) -(org-defkey org-agenda-mode-map "d" 'org-agenda-day-view) -(org-defkey org-agenda-mode-map "w" 'org-agenda-week-view) -(org-defkey org-agenda-mode-map "y" 'org-agenda-year-view) -(org-defkey org-agenda-mode-map "\C-c\C-z" 'org-agenda-add-note) -(org-defkey org-agenda-mode-map "z" 'org-agenda-add-note) -(org-defkey org-agenda-mode-map [(shift right)] 'org-agenda-do-date-later) -(org-defkey org-agenda-mode-map [(shift left)] 'org-agenda-do-date-earlier) -(org-defkey org-agenda-mode-map [?\C-c ?\C-x (right)] 'org-agenda-do-date-later) -(org-defkey org-agenda-mode-map [?\C-c ?\C-x (left)] 'org-agenda-do-date-earlier) - -(org-defkey org-agenda-mode-map ">" 'org-agenda-date-prompt) -(org-defkey org-agenda-mode-map "\C-c\C-s" 'org-agenda-schedule) -(org-defkey org-agenda-mode-map "\C-c\C-d" 'org-agenda-deadline) +(org-defkey org-agenda-mode-map "\C-i" #'org-agenda-goto) +(org-defkey org-agenda-mode-map [(tab)] #'org-agenda-goto) +(org-defkey org-agenda-mode-map "\C-m" #'org-agenda-switch-to) +(org-defkey org-agenda-mode-map "\C-k" #'org-agenda-kill) +(org-defkey org-agenda-mode-map "\C-c\C-w" #'org-agenda-refile) +(org-defkey org-agenda-mode-map [(meta down)] #'org-agenda-drag-line-forward) +(org-defkey org-agenda-mode-map [(meta up)] #'org-agenda-drag-line-backward) +(org-defkey org-agenda-mode-map "m" #'org-agenda-bulk-mark) +(org-defkey org-agenda-mode-map "\M-m" #'org-agenda-bulk-toggle) +(org-defkey org-agenda-mode-map "*" #'org-agenda-bulk-mark-all) +(org-defkey org-agenda-mode-map "\M-*" #'org-agenda-bulk-toggle-all) +(org-defkey org-agenda-mode-map "#" #'org-agenda-dim-blocked-tasks) +(org-defkey org-agenda-mode-map "%" #'org-agenda-bulk-mark-regexp) +(org-defkey org-agenda-mode-map "u" #'org-agenda-bulk-unmark) +(org-defkey org-agenda-mode-map "U" #'org-agenda-bulk-unmark-all) +(org-defkey org-agenda-mode-map "B" #'org-agenda-bulk-action) +(org-defkey org-agenda-mode-map "k" #'org-agenda-capture) +(org-defkey org-agenda-mode-map "A" #'org-agenda-append-agenda) +(org-defkey org-agenda-mode-map "\C-c\C-x!" #'org-reload) +(org-defkey org-agenda-mode-map "\C-c\C-x\C-a" #'org-agenda-archive-default) +(org-defkey org-agenda-mode-map "\C-c\C-xa" #'org-agenda-toggle-archive-tag) +(org-defkey org-agenda-mode-map "\C-c\C-xA" #'org-agenda-archive-to-archive-sibling) +(org-defkey org-agenda-mode-map "\C-c\C-x\C-s" #'org-agenda-archive) +(org-defkey org-agenda-mode-map "\C-c$" #'org-agenda-archive) +(org-defkey org-agenda-mode-map "$" #'org-agenda-archive) +(org-defkey org-agenda-mode-map "\C-c\C-o" #'org-agenda-open-link) +(org-defkey org-agenda-mode-map " " #'org-agenda-show-and-scroll-up) +(org-defkey org-agenda-mode-map [backspace] #'org-agenda-show-scroll-down) +(org-defkey org-agenda-mode-map "\d" #'org-agenda-show-scroll-down) +(org-defkey org-agenda-mode-map [(control shift right)] #'org-agenda-todo-nextset) +(org-defkey org-agenda-mode-map [(control shift left)] #'org-agenda-todo-previousset) +(org-defkey org-agenda-mode-map "\C-c\C-xb" #'org-agenda-tree-to-indirect-buffer) +(org-defkey org-agenda-mode-map "o" #'delete-other-windows) +(org-defkey org-agenda-mode-map "L" #'org-agenda-recenter) +(org-defkey org-agenda-mode-map "\C-c\C-t" #'org-agenda-todo) +(org-defkey org-agenda-mode-map "t" #'org-agenda-todo) +(org-defkey org-agenda-mode-map "a" #'org-agenda-archive-default-with-confirmation) +(org-defkey org-agenda-mode-map ":" #'org-agenda-set-tags) +(org-defkey org-agenda-mode-map "\C-c\C-q" #'org-agenda-set-tags) +(org-defkey org-agenda-mode-map "." #'org-agenda-goto-today) +(org-defkey org-agenda-mode-map "j" #'org-agenda-goto-date) +(org-defkey org-agenda-mode-map "d" #'org-agenda-day-view) +(org-defkey org-agenda-mode-map "w" #'org-agenda-week-view) +(org-defkey org-agenda-mode-map "y" #'org-agenda-year-view) +(org-defkey org-agenda-mode-map "\C-c\C-z" #'org-agenda-add-note) +(org-defkey org-agenda-mode-map "z" #'org-agenda-add-note) +(org-defkey org-agenda-mode-map [(shift right)] #'org-agenda-do-date-later) +(org-defkey org-agenda-mode-map [(shift left)] #'org-agenda-do-date-earlier) +(org-defkey org-agenda-mode-map [?\C-c ?\C-x (right)] #'org-agenda-do-date-later) +(org-defkey org-agenda-mode-map [?\C-c ?\C-x (left)] #'org-agenda-do-date-earlier) +(org-defkey org-agenda-mode-map ">" #'org-agenda-date-prompt) +(org-defkey org-agenda-mode-map "\C-c\C-s" #'org-agenda-schedule) +(org-defkey org-agenda-mode-map "\C-c\C-d" #'org-agenda-deadline) (let ((l '(1 2 3 4 5 6 7 8 9 0))) (while l (org-defkey org-agenda-mode-map - (int-to-string (pop l)) 'digit-argument))) - -(org-defkey org-agenda-mode-map "F" 'org-agenda-follow-mode) -(org-defkey org-agenda-mode-map "R" 'org-agenda-clockreport-mode) -(org-defkey org-agenda-mode-map "E" 'org-agenda-entry-text-mode) -(org-defkey org-agenda-mode-map "l" 'org-agenda-log-mode) -(org-defkey org-agenda-mode-map "v" 'org-agenda-view-mode-dispatch) -(org-defkey org-agenda-mode-map "D" 'org-agenda-toggle-diary) -(org-defkey org-agenda-mode-map "!" 'org-agenda-toggle-deadlines) -(org-defkey org-agenda-mode-map "G" 'org-agenda-toggle-time-grid) -(org-defkey org-agenda-mode-map "r" 'org-agenda-redo) -(org-defkey org-agenda-mode-map "g" 'org-agenda-redo-all) -(org-defkey org-agenda-mode-map "e" 'org-agenda-set-effort) -(org-defkey org-agenda-mode-map "\C-c\C-xe" 'org-agenda-set-effort) + (number-to-string (pop l)) #'digit-argument))) +(org-defkey org-agenda-mode-map "F" #'org-agenda-follow-mode) +(org-defkey org-agenda-mode-map "R" #'org-agenda-clockreport-mode) +(org-defkey org-agenda-mode-map "E" #'org-agenda-entry-text-mode) +(org-defkey org-agenda-mode-map "l" #'org-agenda-log-mode) +(org-defkey org-agenda-mode-map "v" #'org-agenda-view-mode-dispatch) +(org-defkey org-agenda-mode-map "D" #'org-agenda-toggle-diary) +(org-defkey org-agenda-mode-map "!" #'org-agenda-toggle-deadlines) +(org-defkey org-agenda-mode-map "G" #'org-agenda-toggle-time-grid) +(org-defkey org-agenda-mode-map "r" #'org-agenda-redo) +(org-defkey org-agenda-mode-map "g" #'org-agenda-redo-all) +(org-defkey org-agenda-mode-map "e" #'org-agenda-set-effort) +(org-defkey org-agenda-mode-map "\C-c\C-xe" #'org-agenda-set-effort) (org-defkey org-agenda-mode-map "\C-c\C-x\C-e" - 'org-clock-modify-effort-estimate) -(org-defkey org-agenda-mode-map "\C-c\C-xp" 'org-agenda-set-property) -(org-defkey org-agenda-mode-map "q" 'org-agenda-quit) -(org-defkey org-agenda-mode-map "Q" 'org-agenda-Quit) -(org-defkey org-agenda-mode-map "x" 'org-agenda-exit) -(org-defkey org-agenda-mode-map "\C-x\C-w" 'org-agenda-write) -(org-defkey org-agenda-mode-map "\C-x\C-s" 'org-save-all-org-buffers) -(org-defkey org-agenda-mode-map "s" 'org-save-all-org-buffers) -(org-defkey org-agenda-mode-map "T" 'org-agenda-show-tags) -(org-defkey org-agenda-mode-map "n" 'org-agenda-next-line) -(org-defkey org-agenda-mode-map "p" 'org-agenda-previous-line) -(org-defkey org-agenda-mode-map "N" 'org-agenda-next-item) -(org-defkey org-agenda-mode-map "P" 'org-agenda-previous-item) -(substitute-key-definition 'next-line 'org-agenda-next-line + #'org-clock-modify-effort-estimate) +(org-defkey org-agenda-mode-map "\C-c\C-xp" #'org-agenda-set-property) +(org-defkey org-agenda-mode-map "q" #'org-agenda-quit) +(org-defkey org-agenda-mode-map "Q" #'org-agenda-Quit) +(org-defkey org-agenda-mode-map "x" #'org-agenda-exit) +(org-defkey org-agenda-mode-map "\C-x\C-w" #'org-agenda-write) +(org-defkey org-agenda-mode-map "\C-x\C-s" #'org-save-all-org-buffers) +(org-defkey org-agenda-mode-map "s" #'org-save-all-org-buffers) +(org-defkey org-agenda-mode-map "T" #'org-agenda-show-tags) +(org-defkey org-agenda-mode-map "n" #'org-agenda-next-line) +(org-defkey org-agenda-mode-map "p" #'org-agenda-previous-line) +(org-defkey org-agenda-mode-map "N" #'org-agenda-next-item) +(org-defkey org-agenda-mode-map "P" #'org-agenda-previous-item) +(substitute-key-definition #'next-line #'org-agenda-next-line org-agenda-mode-map global-map) -(substitute-key-definition 'previous-line 'org-agenda-previous-line +(substitute-key-definition #'previous-line #'org-agenda-previous-line org-agenda-mode-map global-map) -(org-defkey org-agenda-mode-map "\C-c\C-a" 'org-attach) -(org-defkey org-agenda-mode-map "\C-c\C-n" 'org-agenda-next-date-line) -(org-defkey org-agenda-mode-map "\C-c\C-p" 'org-agenda-previous-date-line) -(org-defkey org-agenda-mode-map "\C-c," 'org-agenda-priority) -(org-defkey org-agenda-mode-map "," 'org-agenda-priority) -(org-defkey org-agenda-mode-map "i" 'org-agenda-diary-entry) -(org-defkey org-agenda-mode-map "c" 'org-agenda-goto-calendar) -(org-defkey org-agenda-mode-map "C" 'org-agenda-convert-date) -(org-defkey org-agenda-mode-map "M" 'org-agenda-phases-of-moon) -(org-defkey org-agenda-mode-map "S" 'org-agenda-sunrise-sunset) -(org-defkey org-agenda-mode-map "h" 'org-agenda-holidays) -(org-defkey org-agenda-mode-map "H" 'org-agenda-holidays) -(org-defkey org-agenda-mode-map "\C-c\C-x\C-i" 'org-agenda-clock-in) -(org-defkey org-agenda-mode-map "I" 'org-agenda-clock-in) -(org-defkey org-agenda-mode-map "\C-c\C-x\C-o" 'org-agenda-clock-out) -(org-defkey org-agenda-mode-map "O" 'org-agenda-clock-out) -(org-defkey org-agenda-mode-map "\C-c\C-x\C-x" 'org-agenda-clock-cancel) -(org-defkey org-agenda-mode-map "X" 'org-agenda-clock-cancel) -(org-defkey org-agenda-mode-map "\C-c\C-x\C-j" 'org-clock-goto) -(org-defkey org-agenda-mode-map "J" 'org-agenda-clock-goto) -(org-defkey org-agenda-mode-map "+" 'org-agenda-priority-up) -(org-defkey org-agenda-mode-map "-" 'org-agenda-priority-down) -(org-defkey org-agenda-mode-map [(shift up)] 'org-agenda-priority-up) -(org-defkey org-agenda-mode-map [(shift down)] 'org-agenda-priority-down) -(org-defkey org-agenda-mode-map [?\C-c ?\C-x (up)] 'org-agenda-priority-up) -(org-defkey org-agenda-mode-map [?\C-c ?\C-x (down)] 'org-agenda-priority-down) -(org-defkey org-agenda-mode-map "f" 'org-agenda-later) -(org-defkey org-agenda-mode-map "b" 'org-agenda-earlier) -(org-defkey org-agenda-mode-map "\C-c\C-x\C-c" 'org-agenda-columns) -(org-defkey org-agenda-mode-map "\C-c\C-x>" 'org-agenda-remove-restriction-lock) -(org-defkey org-agenda-mode-map "\C-c\C-x<" 'org-agenda-set-restriction-lock-from-agenda) - -(org-defkey org-agenda-mode-map "[" 'org-agenda-manipulate-query-add) -(org-defkey org-agenda-mode-map "]" 'org-agenda-manipulate-query-subtract) -(org-defkey org-agenda-mode-map "{" 'org-agenda-manipulate-query-add-re) -(org-defkey org-agenda-mode-map "}" 'org-agenda-manipulate-query-subtract-re) -(org-defkey org-agenda-mode-map "\\" 'org-agenda-filter-by-tag) -(org-defkey org-agenda-mode-map "_" 'org-agenda-filter-by-effort) -(org-defkey org-agenda-mode-map "=" 'org-agenda-filter-by-regexp) -(org-defkey org-agenda-mode-map "/" 'org-agenda-filter) -(org-defkey org-agenda-mode-map "|" 'org-agenda-filter-remove-all) -(org-defkey org-agenda-mode-map "~" 'org-agenda-limit-interactively) -(org-defkey org-agenda-mode-map "<" 'org-agenda-filter-by-category) -(org-defkey org-agenda-mode-map "^" 'org-agenda-filter-by-top-headline) -(org-defkey org-agenda-mode-map ";" 'org-timer-set-timer) -(org-defkey org-agenda-mode-map "\C-c\C-x_" 'org-timer-stop) -(define-key org-agenda-mode-map "?" 'org-agenda-show-the-flagging-note) -(org-defkey org-agenda-mode-map "\C-c\C-x\C-mg" 'org-mobile-pull) -(org-defkey org-agenda-mode-map "\C-c\C-x\C-mp" 'org-mobile-push) -(org-defkey org-agenda-mode-map "\C-c\C-xI" 'org-info-find-node) - -(org-defkey org-agenda-mode-map [mouse-2] 'org-agenda-goto-mouse) -(org-defkey org-agenda-mode-map [mouse-3] 'org-agenda-show-mouse) - -(define-key org-agenda-mode-map [remap forward-paragraph] 'org-agenda-forward-block) -(define-key org-agenda-mode-map [remap backward-paragraph] 'org-agenda-backward-block) +(org-defkey org-agenda-mode-map "\C-c\C-a" #'org-attach) +(org-defkey org-agenda-mode-map "\C-c\C-n" #'org-agenda-next-date-line) +(org-defkey org-agenda-mode-map "\C-c\C-p" #'org-agenda-previous-date-line) +(org-defkey org-agenda-mode-map "\C-c," #'org-agenda-priority) +(org-defkey org-agenda-mode-map "," #'org-agenda-priority) +(org-defkey org-agenda-mode-map "i" #'org-agenda-diary-entry) +(org-defkey org-agenda-mode-map "c" #'org-agenda-goto-calendar) +(org-defkey org-agenda-mode-map "C" #'org-agenda-convert-date) +(org-defkey org-agenda-mode-map "M" #'org-agenda-phases-of-moon) +(org-defkey org-agenda-mode-map "S" #'org-agenda-sunrise-sunset) +(org-defkey org-agenda-mode-map "h" #'org-agenda-holidays) +(org-defkey org-agenda-mode-map "H" #'org-agenda-holidays) +(org-defkey org-agenda-mode-map "\C-c\C-x\C-i" #'org-agenda-clock-in) +(org-defkey org-agenda-mode-map "I" #'org-agenda-clock-in) +(org-defkey org-agenda-mode-map "\C-c\C-x\C-o" #'org-agenda-clock-out) +(org-defkey org-agenda-mode-map "O" #'org-agenda-clock-out) +(org-defkey org-agenda-mode-map "\C-c\C-x\C-x" #'org-agenda-clock-cancel) +(org-defkey org-agenda-mode-map "X" #'org-agenda-clock-cancel) +(org-defkey org-agenda-mode-map "\C-c\C-x\C-j" #'org-clock-goto) +(org-defkey org-agenda-mode-map "J" #'org-agenda-clock-goto) +(org-defkey org-agenda-mode-map "+" #'org-agenda-priority-up) +(org-defkey org-agenda-mode-map "-" #'org-agenda-priority-down) +(org-defkey org-agenda-mode-map [(shift up)] #'org-agenda-priority-up) +(org-defkey org-agenda-mode-map [(shift down)] #'org-agenda-priority-down) +(org-defkey org-agenda-mode-map [?\C-c ?\C-x (up)] #'org-agenda-priority-up) +(org-defkey org-agenda-mode-map [?\C-c ?\C-x (down)] #'org-agenda-priority-down) +(org-defkey org-agenda-mode-map "f" #'org-agenda-later) +(org-defkey org-agenda-mode-map "b" #'org-agenda-earlier) +(org-defkey org-agenda-mode-map "\C-c\C-x\C-c" #'org-agenda-columns) +(org-defkey org-agenda-mode-map "\C-c\C-x>" #'org-agenda-remove-restriction-lock) +(org-defkey org-agenda-mode-map "\C-c\C-x<" #'org-agenda-set-restriction-lock-from-agenda) +(org-defkey org-agenda-mode-map "[" #'org-agenda-manipulate-query-add) +(org-defkey org-agenda-mode-map "]" #'org-agenda-manipulate-query-subtract) +(org-defkey org-agenda-mode-map "{" #'org-agenda-manipulate-query-add-re) +(org-defkey org-agenda-mode-map "}" #'org-agenda-manipulate-query-subtract-re) +(org-defkey org-agenda-mode-map "\\" #'org-agenda-filter-by-tag) +(org-defkey org-agenda-mode-map "_" #'org-agenda-filter-by-effort) +(org-defkey org-agenda-mode-map "=" #'org-agenda-filter-by-regexp) +(org-defkey org-agenda-mode-map "/" #'org-agenda-filter) +(org-defkey org-agenda-mode-map "|" #'org-agenda-filter-remove-all) +(org-defkey org-agenda-mode-map "~" #'org-agenda-limit-interactively) +(org-defkey org-agenda-mode-map "<" #'org-agenda-filter-by-category) +(org-defkey org-agenda-mode-map "^" #'org-agenda-filter-by-top-headline) +(org-defkey org-agenda-mode-map ";" #'org-timer-set-timer) +(org-defkey org-agenda-mode-map "\C-c\C-x_" #'org-timer-stop) +(org-defkey org-agenda-mode-map "?" #'org-agenda-show-the-flagging-note) +(org-defkey org-agenda-mode-map "\C-c\C-x\C-mg" #'org-mobile-pull) +(org-defkey org-agenda-mode-map "\C-c\C-x\C-mp" #'org-mobile-push) +(org-defkey org-agenda-mode-map "\C-c\C-xI" #'org-info-find-node) +(org-defkey org-agenda-mode-map [mouse-2] #'org-agenda-goto-mouse) +(org-defkey org-agenda-mode-map [mouse-3] #'org-agenda-show-mouse) +(org-defkey org-agenda-mode-map [remap forward-paragraph] #'org-agenda-forward-block) +(org-defkey org-agenda-mode-map [remap backward-paragraph] #'org-agenda-backward-block) +(org-defkey org-agenda-mode-map "\C-c\C-c" #'org-agenda-ctrl-c-ctrl-c) (when org-agenda-mouse-1-follows-link (org-defkey org-agenda-mode-map [follow-link] 'mouse-face)) + (easy-menu-define org-agenda-menu org-agenda-mode-map "Agenda menu" '("Agenda" ("Agenda Files") @@ -2563,7 +2592,7 @@ The following commands are available: ["Set Priority" org-agenda-priority t] ["Increase Priority" org-agenda-priority-up t] ["Decrease Priority" org-agenda-priority-down t] - ["Show Priority" org-show-priority t]) + ["Show Priority" org-priority-show t]) ("Calendar/Diary" ["New Diary Entry" org-agenda-diary-entry (org-agenda-check-type nil 'agenda)] ["Goto Calendar" org-agenda-goto-calendar (org-agenda-check-type nil 'agenda)] @@ -2995,7 +3024,8 @@ Agenda views are separated by `org-agenda-block-separator'." (erase-buffer) (insert (eval-when-compile (let ((header - "Press key for an agenda command: + (copy-sequence + "Press key for an agenda command: -------------------------------- < Buffer, subtree/region restriction a Agenda for current week or day > Remove restriction t List of all TODO entries e Export agenda views @@ -3004,7 +3034,7 @@ s Search for keywords M Like m, but only TODO entries / Multi-occur S Like s, but only TODO entries ? Find :FLAGGED: entries C Configure custom agenda commands * Toggle sticky agenda views # List stuck projects (!=configure) -") +")) (start 0)) (while (string-match "\\(^\\| \\|(\\)\\(\\S-\\)\\( \\|=\\)" @@ -3112,7 +3142,7 @@ s Search for keywords M Like m, but only TODO entries ;; Hint to navigation if window too small for all information (setq header-line-format (when (not (pos-visible-in-window-p (point-max))) - "Use SPC, DEL, C-n or C-p to navigate.")) + "Use C-v, M-v, C-n or C-p to navigate.")) ;; Ask for selection (cl-loop @@ -3126,24 +3156,8 @@ s Search for keywords M Like m, but only TODO entries " (unrestricted)")) "")) (setq c (read-char-exclusive))) - until (not (memq c '(14 16 ?\s ?\d))) - do (cl-case c - (14 (if (not (pos-visible-in-window-p (point-max))) - (ignore-errors (scroll-up 1)) - (message "End of buffer") - (sit-for 1))) - (16 (if (not (pos-visible-in-window-p (point-min))) - (ignore-errors (scroll-down 1)) - (message "Beginning of buffer") - (sit-for 1))) - (?\s (if (not (pos-visible-in-window-p (point-max))) - (scroll-up nil) - (message "End of buffer") - (sit-for 1))) - (?\d (if (not (pos-visible-in-window-p (point-min))) - (scroll-down nil) - (message "Beginning of buffer") - (sit-for 1))))) + until (not (memq c '(14 16 22 134217846))) + do (org-scroll c)) (message "") (cond @@ -3590,8 +3604,7 @@ removed from the entry content. Currently only `planning' is allowed here." (when org-agenda-add-entry-text-descriptive-links (goto-char (point-min)) (while (org-activate-links (point-max)) - (add-text-properties (match-beginning 0) (match-end 0) - '(face org-link)))) + (goto-char (match-end 0)))) (goto-char (point-min)) (while (re-search-forward org-link-bracket-re (point-max) t) (set-text-properties (match-beginning 0) (match-end 0) @@ -3746,6 +3759,14 @@ generating a new one." ;; does not have org variables local org-agenda-this-buffer-is-sticky)))) +(defvar org-agenda-buffer-tmp-name nil) + +(defun org-agenda--get-buffer-name (sticky-name) + (or org-agenda-buffer-tmp-name + (and org-agenda-doing-sticky-redo org-agenda-buffer-name) + sticky-name + "*Org Agenda*")) + (defun org-agenda-prepare-window (abuf filter-alist) "Setup agenda buffer in the window. ABUF is the buffer for the agenda window. @@ -3762,6 +3783,10 @@ FILTER-ALIST is an alist of filters we need to apply when (org-switch-to-buffer-other-window abuf)) ((eq org-agenda-window-setup 'other-frame) (switch-to-buffer-other-frame abuf)) + ((eq org-agenda-window-setup 'other-tab) + (if (fboundp 'switch-to-buffer-other-tab) + (switch-to-buffer-other-tab abuf) + (user-error "Your version of Emacs does not have tab bar support"))) ((eq org-agenda-window-setup 'only-window) (delete-other-windows) (pop-to-buffer-same-window abuf)) @@ -3846,15 +3871,17 @@ FILTER-ALIST is an alist of filters we need to apply when (defvar org-overriding-columns-format) (defvar org-local-columns-format) (defun org-agenda-finalize () - "Finishing touch for the agenda buffer, called just before displaying it." + "Finishing touch for the agenda buffer. +This function is called just before displaying the agenda. If +you want to add your own functions to the finalization of the +agenda display, configure `org-agenda-finalize-hook'." (unless org-agenda-multi - (save-excursion - (let ((inhibit-read-only t)) + (let ((inhibit-read-only t)) + (save-excursion (goto-char (point-min)) (save-excursion (while (org-activate-links (point-max)) - (add-text-properties (match-beginning 0) (match-end 0) - '(face org-link)))) + (goto-char (match-end 0)))) (unless (eq org-agenda-remove-tags t) (org-agenda-align-tags)) (unless org-agenda-with-colors @@ -3893,7 +3920,6 @@ FILTER-ALIST is an alist of filters we need to apply when 'tags (org-with-point-at mrk (mapcar #'downcase (org-get-tags))))))))) - (run-hooks 'org-agenda-finalize-hook) (setq org-agenda-represented-tags nil org-agenda-represented-categories nil) (when org-agenda-top-headline-filter @@ -3919,12 +3945,13 @@ FILTER-ALIST is an alist of filters we need to apply when (when (get 'org-agenda-effort-filter :preset-filter) (org-agenda-filter-apply (get 'org-agenda-effort-filter :preset-filter) 'effort)) - (add-hook 'kill-buffer-hook 'org-agenda-reset-markers 'append 'local))))) + (add-hook 'kill-buffer-hook 'org-agenda-reset-markers 'append 'local)) + (run-hooks 'org-agenda-finalize-hook)))) (defun org-agenda-mark-clocking-task () "Mark the current clock entry in the agenda if it is present." ;; We need to widen when `org-agenda-finalize' is called from - ;; `org-agenda-change-all-lines' (e.g. in `org-agenda-clock-in') + ;; `org-agenda-change-all-lines' (e.g. in `org-agenda-clock-in'). (when (bound-and-true-p org-clock-current-task) (save-restriction (widen) @@ -3959,15 +3986,15 @@ FILTER-ALIST is an alist of filters we need to apply when (save-excursion (let (b e p ov h l) (goto-char (point-min)) - (while (re-search-forward "\\[#\\(.\\)\\]" nil t) - (setq h (or (get-char-property (point) 'org-highest-priority) - org-highest-priority) - l (or (get-char-property (point) 'org-lowest-priority) - org-lowest-priority) - p (string-to-char (match-string 1)) - b (match-beginning 0) + (while (re-search-forward org-priority-regexp nil t) + (setq h (or (get-char-property (point) 'org-priority-highest) + org-priority-highest) + l (or (get-char-property (point) 'org-priority-lowest) + org-priority-lowest) + p (string-to-char (match-string 2)) + b (match-beginning 1) e (if (eq org-agenda-fontify-priorities 'cookies) - (match-end 0) + (1+ (match-end 2)) (point-at-eol)) ov (make-overlay b e)) (overlay-put @@ -3995,7 +4022,7 @@ dimming them." (when (called-interactively-p 'interactive) (message "Dim or hide blocked tasks...")) (dolist (o (overlays-in (point-min) (point-max))) - (when (eq (overlay-get o 'org-type) 'org-blocked-todo) + (when (eq (overlay-get o 'face) 'org-agenda-dimmed-todo-face) (delete-overlay o))) (save-excursion (let ((inhibit-read-only t)) @@ -4003,22 +4030,26 @@ dimming them." (while (let ((pos (text-property-not-all (point) (point-max) 'org-todo-blocked nil))) (when pos (goto-char pos))) - (let* ((invisible (eq (org-get-at-bol 'org-todo-blocked) 'invisible)) + (let* ((invisible + (eq (org-get-at-bol 'org-todo-blocked) 'invisible)) + (todo-blocked + (eq (org-get-at-bol 'org-filter-type) 'todo-blocked)) (ov (make-overlay (if invisible (line-end-position 0) (line-beginning-position)) (line-end-position)))) - (if invisible - (overlay-put ov 'invisible t) + (when todo-blocked (overlay-put ov 'face 'org-agenda-dimmed-todo-face)) - (overlay-put ov 'org-type 'org-blocked-todo)) - (forward-line)))) + (when invisible + (org-agenda-filter-hide-line 'todo-blocked))) + (move-beginning-of-line 2)))) (when (called-interactively-p 'interactive) (message "Dim or hide blocked tasks...done"))) (defun org-agenda--mark-blocked-entry (entry) - "For ENTRY a string with the text property `org-hd-marker', if -the header at `org-hd-marker' is blocked according to + "If ENTRY is blocked, mark it for fontification or invisibility. + +If the header at `org-hd-marker' is blocked according to `org-entry-blocked-p', then if `org-agenda-dim-blocked-tasks' is 'invisible and the header is not blocked by checkboxes, set the text property `org-todo-blocked' to `invisible', otherwise set it @@ -4042,7 +4073,9 @@ to t." (put-text-property 0 (length entry) 'org-todo-blocked (if really-invisible 'invisible t) - entry))))))) + entry) + (put-text-property + 0 (length entry) 'org-filter-type 'todo-blocked entry))))))) entry) (defvar org-agenda-skip-function nil @@ -4066,8 +4099,10 @@ continue from there." (when (or (save-excursion (goto-char p) (looking-at comment-start-skip)) (and org-agenda-skip-archived-trees (not org-agenda-archives-mode) - (get-text-property p :org-archived) - (org-end-of-subtree t)) + (or (and (get-text-property p :org-archived) + (org-end-of-subtree t)) + (and (member org-archive-tag org-file-tags) + (goto-char (point-max))))) (and org-agenda-skip-comment-trees (get-text-property p :org-comment) (org-end-of-subtree t)) @@ -4099,8 +4134,8 @@ functions do." (defun org-agenda-new-marker (&optional pos) "Return a new agenda marker. -Maker is at point, or at POS if non-nil. Org mode keeps a list of -these markers and resets them when they are no longer in use." +Marker is at point, or at POS if non-nil. Org mode keeps a list +of these markers and resets them when they are no longer in use." (let ((m (copy-marker (or pos (point)) t))) (setq org-agenda-last-marker-time (float-time)) (if org-agenda-buffer @@ -4182,7 +4217,6 @@ See the docstring of `org-read-date' for details.") (defvar org-starting-day nil) ; local variable in the agenda buffer (defvar org-arg-loc nil) ; local variable -(defvar org-agenda-buffer-tmp-name nil) ;;;###autoload (defun org-agenda-list (&optional arg start-day span with-hour) "Produce a daily/weekly view from all files in variable `org-agenda-files'. @@ -4210,15 +4244,13 @@ items if they have an hour specification like [h]h:mm." (user-error "Agenda creation impossible for this span(=%d days)." span))) (catch 'exit (setq org-agenda-buffer-name - (or org-agenda-buffer-tmp-name - (and org-agenda-doing-sticky-redo org-agenda-buffer-name) - (when org-agenda-sticky + (org-agenda--get-buffer-name + (and org-agenda-sticky (cond ((and org-keys (stringp org-match)) (format "*Org Agenda(%s:%s)*" org-keys org-match)) (org-keys (format "*Org Agenda(%s)*" org-keys)) - (t "*Org Agenda(a)*"))) - "*Org Agenda*")) + (t "*Org Agenda(a)*"))))) (org-agenda-prepare "Day/Week") (setq start-day (or start-day org-agenda-start-day)) (when (stringp start-day) @@ -4365,7 +4397,7 @@ items if they have an hour specification like [h]h:mm." (insert tbl))) (goto-char (point-min)) (or org-agenda-multi (org-agenda-fit-window-to-buffer)) - (unless (or (not (get-buffer-window)) + (unless (or (not (get-buffer-window org-agenda-buffer-name)) (and (pos-visible-in-window-p (point-min)) (pos-visible-in-window-p (point-max)))) (goto-char (1- (point-max))) @@ -4508,12 +4540,15 @@ is active." (edit-at string)) 'org-agenda-search-history))) (catch 'exit - (when org-agenda-sticky - (setq org-agenda-buffer-name - (if (stringp string) - (format "*Org Agenda(%s:%s)*" - (or org-keys (or (and todo-only "S") "s")) string) - (format "*Org Agenda(%s)*" (or (and todo-only "S") "s"))))) + (setq org-agenda-buffer-name + (org-agenda--get-buffer-name + (and org-agenda-sticky + (if (stringp string) + (format "*Org Agenda(%s:%s)*" + (or org-keys (or (and todo-only "S") "s")) + string) + (format "*Org Agenda(%s)*" + (or (and todo-only "S") "s")))))) (org-agenda-prepare "SEARCH") (org-compile-prefix-format 'search) (org-set-sorting-strategy 'search) @@ -4760,12 +4795,13 @@ for a keyword. A numeric prefix directly selects the Nth keyword in (completion-ignore-case t) kwds org-select-this-todo-keyword rtn rtnall files file pos) (catch 'exit - (when org-agenda-sticky - (setq org-agenda-buffer-name - (if (stringp org-select-this-todo-keyword) - (format "*Org Agenda(%s:%s)*" (or org-keys "t") - org-select-this-todo-keyword) - (format "*Org Agenda(%s)*" (or org-keys "t"))))) + (setq org-agenda-buffer-name + (org-agenda--get-buffer-name + (and org-agenda-sticky + (if (stringp org-select-this-todo-keyword) + (format "*Org Agenda(%s:%s)*" (or org-keys "t") + org-select-this-todo-keyword) + (format "*Org Agenda(%s)*" (or org-keys "t")))))) (org-agenda-prepare "TODO") (setq kwds org-todo-keywords-for-agenda org-select-this-todo-keyword (if (stringp arg) arg @@ -4774,8 +4810,12 @@ for a keyword. A numeric prefix directly selects the Nth keyword in (nth (1- arg) kwds)))) (when (equal arg '(4)) (setq org-select-this-todo-keyword - (completing-read "Keyword (or KWD1|K2D2|...): " - (mapcar #'list kwds) nil nil))) + (mapconcat #'identity + (let ((crm-separator "|")) + (completing-read-multiple + "Keyword (or KWD1|KWD2|...): " + (mapcar #'list kwds) nil nil)) + "|"))) (and (equal 0 arg) (setq org-select-this-todo-keyword nil)) (org-compile-prefix-format 'todo) (org-set-sorting-strategy 'todo) @@ -4848,13 +4888,15 @@ The prefix arg TODO-ONLY limits the search to TODO entries." (when (and (stringp match) (not (string-match "\\S-" match))) (setq match nil)) (catch 'exit - ;; TODO: this code is repeated a lot... - (when org-agenda-sticky - (setq org-agenda-buffer-name - (if (stringp match) - (format "*Org Agenda(%s:%s)*" - (or org-keys (or (and todo-only "M") "m")) match) - (format "*Org Agenda(%s)*" (or (and todo-only "M") "m"))))) + (setq org-agenda-buffer-name + (org-agenda--get-buffer-name + (and org-agenda-sticky + (if (stringp match) + (format "*Org Agenda(%s:%s)*" + (or org-keys (or (and todo-only "M") "m")) + match) + (format "*Org Agenda(%s)*" + (or (and todo-only "M") "m")))))) (setq matcher (org-make-tags-matcher match)) ;; Prepare agendas (and `org-tag-alist-for-agenda') before ;; expanding tags within `org-make-tags-matcher' @@ -5135,6 +5177,7 @@ of what a project is and how to check if it stuck, customize the variable (cons 'org-diary-default-entry diary-list-entries-hook)) (diary-file-name-prefix nil) ; turn this feature off (diary-modify-entry-list-string-function 'org-modify-diary-entry-string) + (diary-time-regexp (concat "^" diary-time-regexp)) entries (org-disable-agenda-to-diary t)) (save-excursion @@ -5284,7 +5327,8 @@ function from a program - use `org-agenda-get-day-entries' instead." (when results (setq results (mapcar (lambda (i) (replace-regexp-in-string - org-link-bracket-re "\\2" i)) results)) + org-link-bracket-re "\\2" i)) + results)) (concat (org-agenda-finalize-entries results) "\n")))) ;;; Agenda entry finders @@ -5503,10 +5547,12 @@ This function is invoked if `org-agenda-todo-ignore-deadlines', (cond ((eq org-agenda-todo-ignore-scheduled 'future) (> (org-time-stamp-to-now - (match-string 1) org-agenda-todo-ignore-time-comparison-use-seconds) 0)) + (match-string 1) org-agenda-todo-ignore-time-comparison-use-seconds) + 0)) ((eq org-agenda-todo-ignore-scheduled 'past) (<= (org-time-stamp-to-now - (match-string 1) org-agenda-todo-ignore-time-comparison-use-seconds) 0)) + (match-string 1) org-agenda-todo-ignore-time-comparison-use-seconds) + 0)) ((numberp org-agenda-todo-ignore-scheduled) (org-agenda-todo-custom-ignore-p (match-string 1) org-agenda-todo-ignore-scheduled)) @@ -5519,10 +5565,12 @@ This function is invoked if `org-agenda-todo-ignore-deadlines', (not (org-deadline-close-p (match-string 1)))) ((eq org-agenda-todo-ignore-deadlines 'future) (> (org-time-stamp-to-now - (match-string 1) org-agenda-todo-ignore-time-comparison-use-seconds) 0)) + (match-string 1) org-agenda-todo-ignore-time-comparison-use-seconds) + 0)) ((eq org-agenda-todo-ignore-deadlines 'past) (<= (org-time-stamp-to-now - (match-string 1) org-agenda-todo-ignore-time-comparison-use-seconds) 0)) + (match-string 1) org-agenda-todo-ignore-time-comparison-use-seconds) + 0)) ((numberp org-agenda-todo-ignore-deadlines) (org-agenda-todo-custom-ignore-p (match-string 1) org-agenda-todo-ignore-deadlines)) @@ -5546,10 +5594,12 @@ This function is invoked if `org-agenda-todo-ignore-deadlines', (cond ((eq org-agenda-todo-ignore-timestamp 'future) (> (org-time-stamp-to-now - (match-string 1) org-agenda-todo-ignore-time-comparison-use-seconds) 0)) + (match-string 1) org-agenda-todo-ignore-time-comparison-use-seconds) + 0)) ((eq org-agenda-todo-ignore-timestamp 'past) (<= (org-time-stamp-to-now - (match-string 1) org-agenda-todo-ignore-time-comparison-use-seconds) 0)) + (match-string 1) org-agenda-todo-ignore-time-comparison-use-seconds) + 0)) ((numberp org-agenda-todo-ignore-timestamp) (org-agenda-todo-custom-ignore-p (match-string 1) org-agenda-todo-ignore-timestamp)) @@ -6457,7 +6507,6 @@ scheduled items with an hour specification like [h]h:mm." (and (eq org-agenda-show-inherited-tags t) (or (eq org-agenda-use-tag-inheritance t) (memq 'agenda org-agenda-use-tag-inheritance)))) - tags (org-get-tags nil (not inherited-tags))) (setq level (make-string (org-reduced-level (org-outline-level)) ? )) (looking-at "\\*+[ \t]+\\(.*\\)") @@ -6475,12 +6524,19 @@ scheduled items with an hour specification like [h]h:mm." org-agenda-timerange-leaders) (1+ (- d0 d1)) (1+ (- d2 d1))) head level category tags - (cond ((and (= d1 d0) (= d2 d0)) - (concat "<" start-time ">--<" end-time ">")) - ((= d1 d0) - (concat "<" start-time ">")) - ((= d2 d0) - (concat "<" end-time ">"))) + (save-match-data + (let ((hhmm1 (and (string-match org-ts-regexp1 s1) + (match-string 6 s1))) + (hhmm2 (and (string-match org-ts-regexp1 s2) + (match-string 6 s2)))) + (cond ((string= hhmm1 hhmm2) + (concat "<" start-time ">--<" end-time ">")) + ((and (= d1 d0) (= d2 d0)) + (concat "<" start-time ">--<" end-time ">")) + ((= d1 d0) + (concat "<" start-time ">")) + ((= d2 d0) + (concat "<" end-time ">"))))) remove-re)))) (org-add-props txt props 'org-marker marker 'org-hd-marker hdmarker @@ -6676,8 +6732,8 @@ Any match of REMOVE-RE will be removed from TXT." (org-add-props rtn nil 'org-category category 'tags (mapcar 'org-downcase-keep-props tags) - 'org-highest-priority org-highest-priority - 'org-lowest-priority org-lowest-priority + 'org-priority-highest org-priority-highest + 'org-priority-lowest org-priority-lowest 'time-of-day time-of-day 'duration duration 'breadcrumbs breadcrumbs @@ -6872,7 +6928,7 @@ HH:MM." (< t0 1000)) "0" "") (if (< t0 100) "0" "") (if (< t0 10) "0" "") - (int-to-string t0)))) + (number-to-string t0)))) (if string (concat (substring t1 -4 -2) ":" (substring t1 -2)) t0))))) (defvar org-agenda-before-sorting-filter-function nil @@ -6920,7 +6976,7 @@ The optional argument TYPE tells the agenda type." (when max-effort (setq list (org-agenda-limit-entries list 'effort-minutes max-effort - (lambda (e) (or e (if org-sort-agenda-noeffort-is-high + (lambda (e) (or e (if org-agenda-sort-noeffort-is-high 32767 -1)))))) (when max-todo (setq list (org-agenda-limit-entries list 'todo-state max-todo))) @@ -7006,7 +7062,8 @@ The optional argument TYPE tells the agenda type." ;; that isn't there. pl (equal (string-match (concat "\\(\\.*\\)" re "\\( +\\)") - x pl) pl)) + x pl) + pl)) (add-text-properties (or (match-end 1) (match-end 0)) (match-end 0) (list 'face (org-get-todo-face (match-string 2 x))) @@ -7033,7 +7090,7 @@ The optional argument TYPE tells the agenda type." (defsubst org-cmp-effort (a b) "Compare the effort values of string A and B." - (let* ((def (if org-sort-agenda-noeffort-is-high 32767 -1)) + (let* ((def (if org-agenda-sort-noeffort-is-high 32767 -1)) ;; `effort-minutes' property is not directly accessible from ;; the strings, but is stored as a property in `txt'. (ea (or (get-text-property @@ -7083,12 +7140,14 @@ The optional argument TYPE tells the agenda type." (case-fold-search nil)) (when pla (when (string-match (concat "\\`[ \t]*" (or (get-text-property 0 'org-todo-regexp a) "") - "\\([ \t]*\\[[a-zA-Z0-9]\\]\\)? *") ta) + "\\([ \t]*\\[[a-zA-Z0-9]\\]\\)? *") + ta) (setq ta (substring ta (match-end 0)))) (setq ta (downcase ta))) (when plb (when (string-match (concat "\\`[ \t]*" (or (get-text-property 0 'org-todo-regexp b) "") - "\\([ \t]*\\[[a-zA-Z0-9]\\]\\)? *") tb) + "\\([ \t]*\\[[a-zA-Z0-9]\\]\\)? *") + tb) (setq tb (substring tb (match-end 0)))) (setq tb (downcase tb))) (cond ((not (or ta tb)) nil) @@ -7109,7 +7168,7 @@ The optional argument TYPE tells the agenda type." (defsubst org-cmp-time (a b) "Compare the time-of-day values of strings A and B." - (let* ((def (if org-sort-agenda-notime-is-late 9901 -1)) + (let* ((def (if org-agenda-sort-notime-is-late 9901 -1)) (ta (or (get-text-property 1 'time-of-day a) def)) (tb (or (get-text-property 1 'time-of-day b) def))) (cond ((< ta tb) -1) @@ -7121,7 +7180,7 @@ When TYPE is \"scheduled\", \"deadline\", \"timestamp\" or \"timestamp_ia\", compare within each of these type. When TYPE is the empty string, compare all timestamps without respect of their type." - (let* ((def (and (not org-sort-agenda-notime-is-late) -1)) + (let* ((def (and (not org-agenda-sort-notime-is-late) -1)) (ta (or (and (string-match type (or (get-text-property 1 'type a) "")) (get-text-property 1 'ts-date a)) def)) @@ -7351,6 +7410,10 @@ agenda." (cond ((eq org-agenda-window-setup 'other-frame) (delete-frame)) + ((eq org-agenda-window-setup 'other-tab) + (if (fboundp 'tab-bar-close-tab) + (tab-bar-close-tab) + (user-error "Your version of Emacs does not have tab bar mode support"))) ((and org-agenda-restore-windows-after-quit wconf) ;; Maybe restore the pre-agenda window configuration. Reset @@ -7470,7 +7533,7 @@ in the agenda." (and top-hl-filter (org-agenda-filter-top-headline-apply top-hl-filter)) (and cols (called-interactively-p 'any) (org-agenda-columns)) (org-goto-line line) - (recenter window-line))) + (when (called-interactively-p 'any) (recenter window-line)))) (defun org-agenda-redo-all (&optional exhaustive) "Rebuild all agenda views in the current buffer. @@ -7508,7 +7571,8 @@ When there is already a category filter in place, this command removes the filte (cat (org-agenda-filter-apply (setq org-agenda-category-filter - (list (concat "+" cat))) 'category)) + (list (concat "+" cat))) + 'category)) (t (error "No category at point")))))) (defun org-find-top-headline (&optional pos) @@ -7520,7 +7584,10 @@ search from." (when pos (goto-char pos)) ;; Skip up to the topmost parent. (while (org-up-heading-safe)) - (ignore-errors (nth 4 (org-heading-components)))))) + (ignore-errors + (replace-regexp-in-string + "^\\[[0-9]+/[0-9]+\\] *\\|^\\[%[0-9]+\\] *" "" + (nth 4 (org-heading-components))))))) (defvar org-agenda-filtered-by-top-headline nil) (defun org-agenda-filter-by-top-headline (strip) @@ -7572,8 +7639,9 @@ This last option is in practice not very useful, but it is available for consistency with the other filter commands." (interactive "P") (let* ((efforts (split-string - (or (cdr (assoc (concat org-effort-property "_ALL") - org-global-properties)) + (or (cdr (assoc-string (concat org-effort-property "_ALL") + org-global-properties + t)) "0 0:10 0:30 1:00 2:00 3:00 4:00 5:00 6:00 7:00"))) ;; XXX: the following handles only up to 10 different ;; effort values. @@ -7614,7 +7682,6 @@ consistency with the other filter commands." (if keep current nil))) (org-agenda-filter-apply org-agenda-effort-filter 'effort))))) - (defun org-agenda-filter (&optional strip-or-accumulate) "Prompt for a general filter string and apply it to the agenda. @@ -7665,11 +7732,18 @@ the variable `org-agenda-auto-exclude-function'." (let* ((tag-list (org-agenda-get-represented-tags)) (category-list (org-agenda-get-represented-categories)) (negate (equal strip-or-accumulate '(4))) + (cf (mapconcat #'identity org-agenda-category-filter "")) + (tf (mapconcat #'identity org-agenda-tag-filter "")) + (rpl-fn (lambda (c) (replace-regexp-in-string "^\+" "" (or (car c) "")))) + (ef (replace-regexp-in-string "^\+" "" (or (car org-agenda-effort-filter) ""))) + (rf (replace-regexp-in-string "^\+" "" (or (car org-agenda-regexp-filter) ""))) + (ff (concat cf tf ef (when (not (equal rf "")) (concat "/" rf "/")))) (f-string (completing-read (concat (if negate "Negative filter" "Filter") " [+cat-tag<0:10-/regexp/]: ") - 'org-agenda-filter-completion-function)) + 'org-agenda-filter-completion-function + nil nil ff)) (keep (or (if (string-match "^\\+[+-]" f-string) (progn (setq f-string (substring f-string 1)) t)) (equal strip-or-accumulate '(16)))) @@ -7678,6 +7752,11 @@ the variable `org-agenda-auto-exclude-function'." (fe (if keep org-agenda-effort-filter)) (fr (if keep org-agenda-regexp-filter)) pm s) + ;; If the filter contains a double-quoted string, replace a + ;; single hyphen by the arbitrary and temporary string "~~~" + ;; to disambiguate such hyphens from syntactic ones. + (setq f-string (replace-regexp-in-string + "\"\\([^\"]*\\)-\\([^\"]*\\)\"" "\"\\1~~~\\2\"" f-string)) (while (string-match "^[ \t]*\\([-+]\\)?\\(\\([^-+<>=/ \t]+\\)\\|\\([<>=][0-9:]+\\)\\|\\(/\\([^/]+\\)/?\\)\\)" f-string) (setq pm (if (match-beginning 1) (match-string 1 f-string) "+")) (when negate @@ -7685,12 +7764,15 @@ the variable `org-agenda-auto-exclude-function'." (cond ((match-beginning 3) ;; category or tag - (setq s (match-string 3 f-string)) + (setq s (replace-regexp-in-string ; Remove the temporary special string. + "~~~" "-" (match-string 3 f-string))) (cond ((member s tag-list) (add-to-list 'ft (concat pm s) 'append 'equal)) ((member s category-list) - (add-to-list 'fc (concat pm s) 'append 'equal)) + (add-to-list 'fc (concat pm ; Remove temporary double quotes. + (replace-regexp-in-string "\"\\(.*\\)\"" "\\1" s)) + 'append 'equal)) (t (message "`%s%s' filter ignored because tag/category is not represented" pm s)))) @@ -7705,15 +7787,15 @@ the variable `org-agenda-auto-exclude-function'." (and fc (org-agenda-filter-apply (setq org-agenda-category-filter fc) 'category)) (and ft (org-agenda-filter-apply - (setq org-agenda-tag-filter ft) 'tag)) + (setq org-agenda-tag-filter ft) 'tag 'expand)) (and fe (org-agenda-filter-apply (setq org-agenda-effort-filter fe) 'effort)) (and fr (org-agenda-filter-apply (setq org-agenda-regexp-filter fr) 'regexp)) - ))) + (run-hooks 'org-agenda-filter-hook)))) (defun org-agenda-filter-completion-function (string _predicate &optional flag) - "Complete a complex filter string + "Complete a complex filter string. FLAG specifies the type of completion operation to perform. This function is passed as a collection function to `completing-read', which see." @@ -7732,8 +7814,9 @@ which see." (org-agenda-get-represented-tags)))) ((member operator '("<" ">" "=")) (setq table (split-string - (or (cdr (assoc (concat org-effort-property "_ALL") - org-global-properties)) + (or (cdr (assoc-string (concat org-effort-property "_ALL") + org-global-properties + t)) "0 0:10 0:30 1:00 2:00 3:00 4:00 5:00 6:00 7:00") " +"))) (t (setq table nil))) @@ -7760,7 +7843,9 @@ which see." (org-agenda-filter-show-all-top-filter)) (when org-agenda-effort-filter (org-agenda-filter-show-all-effort)) - (org-agenda-finalize)) + (org-agenda-finalize) + (when (called-interactively-p 'interactive) + (message "All agenda filters removed"))) (defun org-agenda-filter-by-tag (strip-or-accumulate &optional char exclude) "Keep only those lines in the agenda buffer that have a specific tag. @@ -7860,8 +7945,12 @@ also press `-' or `+' to switch between filtering and excluding." pos 'org-category nil (point-max)))) (push (get-text-property pos 'org-category) categories)) (setq org-agenda-represented-categories - (nreverse (org-uniquify (delq nil categories)))))))) + ;; Enclose category names with a hyphen in double + ;; quotes to process them specially in `org-agenda-filter'. + (mapcar (lambda (s) (if (string-match-p "-" s) (format "\"%s\"" s) s)) + (nreverse (org-uniquify (delq nil categories))))))))) +(defvar org-tag-groups-alist-for-agenda) (defun org-agenda-get-represented-tags () "Return a list of all tags used in this agenda buffer. These will be lower-case, for filtering." @@ -7873,15 +7962,27 @@ These will be lower-case, for filtering." pos 'tags nil (point-max)))) (setq tt (get-text-property pos 'tags)) (if tt (push tt tags-lists))) - (setq org-agenda-represented-tags + (setq tags-lists (nreverse (org-uniquify - (delq nil (apply 'append tags-lists))))))))) + (delq nil (apply 'append tags-lists))))) + (dolist (tag tags-lists) + (mapc + (lambda (group) + (when (member tag (mapcar #'downcase group)) + (push (downcase (car group)) tags-lists))) + org-tag-groups-alist-for-agenda)) + (setq org-agenda-represented-tags tags-lists))))) (defun org-agenda-filter-make-matcher (filter type &optional expand) "Create the form that tests a line for agenda filter. Optional argument EXPAND can be used for the TYPE tag and will expand the tags in the FILTER if any of the tags in FILTER are grouptags." - (let (f f1) + (let ((multi-pos-cats + (and (eq type 'category) + (string-match-p "\\+.*\\+" + (mapconcat (lambda (cat) (substring cat 0 1)) + filter "")))) + f f1) (cond ;; Tag filter ((eq type 'tag) @@ -7925,7 +8026,7 @@ tags in the FILTER if any of the tags in FILTER are grouptags." filter))) (dolist (x filter) (push (org-agenda-filter-effort-form x) f)))) - (cons 'and (nreverse f)))) + (cons (if multi-pos-cats 'or 'and) (nreverse f)))) (defun org-agenda-filter-make-matcher-tag-exp (tags op) "Return a form associated to tag-expression TAGS. @@ -7965,12 +8066,13 @@ If the line does not have an effort defined, return nil." ;; current line but is stored as a property in `txt'. (let ((effort (get-text-property 0 'effort-minutes (org-get-at-bol 'txt)))) (funcall op - (or effort (if org-sort-agenda-noeffort-is-high 32767 -1)) + (or effort (if org-agenda-sort-noeffort-is-high 32767 -1)) value))) (defun org-agenda-filter-expand-tags (filter &optional no-operator) "Expand group tags in FILTER for the agenda. -When NO-OPERATOR is non-nil, do not add the + operator to returned tags." +When NO-OPERATOR is non-nil, do not add the + operator to +returned tags." (if org-group-tags (let ((case-fold-search t) rtn) (mapc @@ -7987,34 +8089,33 @@ When NO-OPERATOR is non-nil, do not add the + operator to returned tags." filter)) (defun org-agenda-filter-apply (filter type &optional expand) - "Set FILTER as the new agenda filter and apply it. Optional -argument EXPAND can be used for the TYPE tag and will expand the -tags in the FILTER if any of the tags in FILTER are grouptags." + "Set FILTER as the new agenda filter and apply it. +Optional argument EXPAND can be used for the TYPE tag and will +expand the tags in the FILTER if any of the tags in FILTER are +grouptags." ;; Deactivate `org-agenda-entry-text-mode' when filtering (when org-agenda-entry-text-mode (org-agenda-entry-text-mode)) - (let (tags cat txt) - (setq org-agenda-filter-form (org-agenda-filter-make-matcher - filter type expand)) - ;; Only set `org-agenda-filtered-by-category' to t when a unique - ;; category is used as the filter: - (setq org-agenda-filtered-by-category - (and (eq type 'category) - (not (equal (substring (car filter) 0 1) "-")))) - (org-agenda-set-mode-name) - (save-excursion - (goto-char (point-min)) - (while (not (eobp)) - (if (org-get-at-bol 'org-hd-marker) - (progn - (setq tags (org-get-at-bol 'tags) - cat (org-agenda-get-category) - txt (org-get-at-bol 'txt)) - (unless (eval org-agenda-filter-form) - (org-agenda-filter-hide-line type)) - (beginning-of-line 2)) - (beginning-of-line 2)))) - (when (get-char-property (point) 'invisible) - (ignore-errors (org-agenda-previous-line))))) + (setq org-agenda-filter-form (org-agenda-filter-make-matcher + filter type expand)) + ;; Only set `org-agenda-filtered-by-category' to t when a unique + ;; category is used as the filter: + (setq org-agenda-filtered-by-category + (and (eq type 'category) + (not (equal (substring (car filter) 0 1) "-")))) + (org-agenda-set-mode-name) + (save-excursion + (goto-char (point-min)) + (while (not (eobp)) + (when (or (org-get-at-bol 'org-hd-marker) + (org-get-at-bol 'org-marker)) + (let ((tags (org-get-at-bol 'tags)) + (cat (org-agenda-get-category)) + (txt (or (org-get-at-bol 'txt) ""))) + (unless (eval org-agenda-filter-form) + (org-agenda-filter-hide-line type)))) + (beginning-of-line 2))) + (when (get-char-property (point) 'invisible) + (ignore-errors (org-agenda-previous-line)))) (defun org-agenda-filter-top-headline-apply (hl &optional negative) "Filter by top headline HL." @@ -8034,16 +8135,17 @@ tags in the FILTER if any of the tags in FILTER are grouptags." org-agenda-filtered-by-top-headline t)) (defun org-agenda-filter-hide-line (type) - "Hide lines with TYPE in the agenda buffer." - (let* ((b (max (point-min) (1- (point-at-bol)))) - (e (point-at-eol))) + "If current line is TYPE, hide it in the agenda buffer." + (let* (buffer-invisibility-spec + (beg (max (point-min) (1- (point-at-bol)))) + (end (point-at-eol))) (let ((inhibit-read-only t)) (add-text-properties - b e `(invisible org-filtered org-filter-type ,type))))) + beg end `(invisible org-filtered org-filter-type ,type))))) (defun org-agenda-remove-filter (type) - (interactive) "Remove filter of type TYPE from the agenda buffer." + (interactive) (save-excursion (goto-char (point-min)) (let ((inhibit-read-only t) pos) @@ -8517,7 +8619,10 @@ log items, nothing else." When called with a prefix argument, include all archive files as well." (interactive "P") (setq org-agenda-archives-mode - (if with-files t (if org-agenda-archives-mode nil 'trees))) + (cond ((and with-files (eq org-agenda-archives-mode t)) nil) + (with-files t) + (org-agenda-archives-mode nil) + (t 'trees))) (org-agenda-set-mode-name) (org-agenda-redo) (message @@ -8584,14 +8689,14 @@ When called with a prefix argument, include all archive files as well." (if (or org-agenda-category-filter (get 'org-agenda-category-filter :preset-filter)) '(:eval (propertize - (concat "[" + (concat "[" (mapconcat 'identity (append (get 'org-agenda-category-filter :preset-filter) org-agenda-category-filter) "") - "]") + "]") 'face 'org-agenda-filter-category 'help-echo "Category used in filtering")) "") (if (or org-agenda-tag-filter @@ -8703,6 +8808,7 @@ When called with a prefix argument, include all archive files as well." (org-agenda-error))) (buffer (marker-buffer marker)) (pos (marker-position marker))) + ;; FIXME: use `org-switch-to-buffer-other-window'? (switch-to-buffer-other-window buffer) (widen) (push-mark) @@ -8721,92 +8827,143 @@ When called with a prefix argument, include all archive files as well." "Normal hook run after an item has been shown from the agenda. Point is in the buffer where the item originated.") +;; Defined later in org-agenda.el +(defvar org-agenda-loop-over-headlines-in-active-region nil) + +(defun org-agenda-do-in-region (beg end cmd &optional arg force-arg delete) + "Between region BEG and END, call agenda command CMD. +When optional argument ARG is non-nil or FORCE-ARG is `t', pass +ARG to CMD. When optional argument DELETE is non-nil, assume CMD +deletes the agenda entry and don't move to the next entry." + (save-excursion + (goto-char beg) + (let ((mend (move-marker (make-marker) end)) + (all (eq org-agenda-loop-over-headlines-in-active-region t)) + (match (and (stringp org-agenda-loop-over-headlines-in-active-region) + org-agenda-loop-over-headlines-in-active-region)) + (level (and (eq org-agenda-loop-over-headlines-in-active-region 'start-level) + (org-get-at-bol 'level)))) + (while (< (point) mend) + (let ((ov (make-overlay (point) (point-at-eol)))) + (if (not (or all + (and match (looking-at-p match)) + (eq level (org-get-at-bol 'level)))) + (org-agenda-next-item 1) + (overlay-put ov 'face 'region) + (if (or arg force-arg) (funcall cmd arg) (funcall cmd)) + (when (not delete) (org-agenda-next-item 1)) + (delete-overlay ov))))))) + +;; org-agenda-[schedule,deadline,date-prompt,todo,[toggle]archive*, +;; kill,set-property,set-effort] commands may loop over agenda +;; entries. Commands `org-agenda-set-tags' and `org-agenda-bulk-mark' +;; use their own mechanisms on active regions. +(defmacro org-agenda-maybe-loop (cmd arg force-arg delete &rest body) + "Maybe loop over agenda entries and perform CMD. +Pass ARG, FORCE-ARG, DELETE and BODY to `org-agenda-do-in-region'." + (declare (debug t)) + `(if (and (called-interactively-p 'any) + org-agenda-loop-over-headlines-in-active-region + (org-region-active-p)) + (org-agenda-do-in-region + (region-beginning) (region-end) ,cmd ,arg ,force-arg ,delete) + ,@body)) + (defun org-agenda-kill () "Kill the entry or subtree belonging to the current agenda entry." (interactive) - (or (eq major-mode 'org-agenda-mode) (error "Not in agenda")) - (let* ((bufname-orig (buffer-name)) - (marker (or (org-get-at-bol 'org-marker) - (org-agenda-error))) - (buffer (marker-buffer marker)) - (pos (marker-position marker)) - (type (org-get-at-bol 'type)) - dbeg dend (n 0)) - (org-with-remote-undo buffer - (with-current-buffer buffer - (save-excursion - (goto-char pos) - (if (and (derived-mode-p 'org-mode) (not (member type '("sexp")))) - (setq dbeg (progn (org-back-to-heading t) (point)) - dend (org-end-of-subtree t t)) - (setq dbeg (point-at-bol) - dend (min (point-max) (1+ (point-at-eol))))) - (goto-char dbeg) - (while (re-search-forward "^[ \t]*\\S-" dend t) (setq n (1+ n))))) - (when (or (eq t org-agenda-confirm-kill) - (and (numberp org-agenda-confirm-kill) - (> n org-agenda-confirm-kill))) - (let ((win-conf (current-window-configuration))) - (unwind-protect - (and - (prog2 - (org-agenda-tree-to-indirect-buffer nil) - (not (y-or-n-p - (format "Delete entry with %d lines in buffer \"%s\"? " - n (buffer-name buffer)))) - (kill-buffer org-last-indirect-buffer)) - (error "Abort")) - (set-window-configuration win-conf)))) - (let ((org-agenda-buffer-name bufname-orig)) - (org-remove-subtree-entries-from-agenda buffer dbeg dend)) - (with-current-buffer buffer (delete-region dbeg dend)) - (message "Agenda item and source killed")))) + (or (eq major-mode 'org-agenda-mode) (user-error "Not in agenda")) + (org-agenda-maybe-loop + #'org-agenda-kill nil nil t + (let* ((bufname-orig (buffer-name)) + (marker (or (org-get-at-bol 'org-marker) + (org-agenda-error))) + (buffer (marker-buffer marker)) + (pos (marker-position marker)) + (type (org-get-at-bol 'type)) + dbeg dend (n 0)) + (org-with-remote-undo buffer + (with-current-buffer buffer + (save-excursion + (goto-char pos) + (if (and (derived-mode-p 'org-mode) (not (member type '("sexp")))) + (setq dbeg (progn (org-back-to-heading t) (point)) + dend (org-end-of-subtree t t)) + (setq dbeg (point-at-bol) + dend (min (point-max) (1+ (point-at-eol))))) + (goto-char dbeg) + (while (re-search-forward "^[ \t]*\\S-" dend t) (setq n (1+ n))))) + (when (or (eq t org-agenda-confirm-kill) + (and (numberp org-agenda-confirm-kill) + (> n org-agenda-confirm-kill))) + (let ((win-conf (current-window-configuration))) + (unwind-protect + (and + (prog2 + (org-agenda-tree-to-indirect-buffer nil) + (not (y-or-n-p + (format "Delete entry with %d lines in buffer \"%s\"? " + n (buffer-name buffer)))) + (kill-buffer org-last-indirect-buffer)) + (error "Abort")) + (set-window-configuration win-conf)))) + (let ((org-agenda-buffer-name bufname-orig)) + (org-remove-subtree-entries-from-agenda buffer dbeg dend)) + (with-current-buffer buffer (delete-region dbeg dend)) + (message "Agenda item and source killed"))))) (defvar org-archive-default-command) ; defined in org-archive.el (defun org-agenda-archive-default () "Archive the entry or subtree belonging to the current agenda entry." (interactive) (require 'org-archive) - (org-agenda-archive-with org-archive-default-command)) + (funcall-interactively + #'org-agenda-archive-with org-archive-default-command)) (defun org-agenda-archive-default-with-confirmation () "Archive the entry or subtree belonging to the current agenda entry." (interactive) (require 'org-archive) - (org-agenda-archive-with org-archive-default-command 'confirm)) + (funcall-interactively + #'org-agenda-archive-with org-archive-default-command 'confirm)) (defun org-agenda-archive () "Archive the entry or subtree belonging to the current agenda entry." (interactive) - (org-agenda-archive-with 'org-archive-subtree)) + (funcall-interactively + #'org-agenda-archive-with 'org-archive-subtree)) (defun org-agenda-archive-to-archive-sibling () "Move the entry to the archive sibling." (interactive) - (org-agenda-archive-with 'org-archive-to-archive-sibling)) + (funcall-interactively + #'org-agenda-archive-with 'org-archive-to-archive-sibling)) (defun org-agenda-archive-with (cmd &optional confirm) "Move the entry to the archive sibling." (interactive) - (or (eq major-mode 'org-agenda-mode) (error "Not in agenda")) - (let* ((bufname-orig (buffer-name)) - (marker (or (org-get-at-bol 'org-marker) - (org-agenda-error))) - (buffer (marker-buffer marker)) - (pos (marker-position marker))) - (org-with-remote-undo buffer - (with-current-buffer buffer - (if (derived-mode-p 'org-mode) - (if (and confirm - (not (y-or-n-p "Archive this subtree or entry? "))) - (error "Abort") - (save-window-excursion - (goto-char pos) - (let ((org-agenda-buffer-name bufname-orig)) - (org-remove-subtree-entries-from-agenda)) - (org-back-to-heading t) - (funcall cmd))) - (error "Archiving works only in Org files")))))) + (or (eq major-mode 'org-agenda-mode) (user-error "Not in agenda")) + (org-agenda-maybe-loop + #'org-agenda-archive-with cmd nil t + (let* ((bufname-orig (buffer-name)) + (marker (or (org-get-at-bol 'org-marker) + (org-agenda-error))) + (buffer (marker-buffer marker)) + (pos (marker-position marker))) + (org-with-remote-undo buffer + (with-current-buffer buffer + (if (derived-mode-p 'org-mode) + (if (and confirm + (not (y-or-n-p "Archive this subtree or entry? "))) + (error "Abort") + (save-window-excursion + (goto-char pos) + (let ((org-agenda-buffer-name bufname-orig)) + (org-remove-subtree-entries-from-agenda)) + (org-back-to-heading t) + (let ((org-archive-from-agenda t)) + (funcall cmd)))) + (error "Archiving works only in Org files"))))))) (defun org-remove-subtree-entries-from-agenda (&optional buf beg end) "Remove all lines in the agenda that correspond to a given subtree. @@ -8893,12 +9050,16 @@ It also looks at the text of the entry itself." (setq trg (and (string-match org-link-bracket-re l) (match-string 1 l))) (if (or (not trg) (string-match org-link-any-re trg)) - (org-with-wide-buffer - (goto-char marker) - (when (search-forward l nil lkend) - (goto-char (match-beginning 0)) - (org-open-at-point))) + ;; Don't use `org-with-wide-buffer' here as + ;; opening the link may result in moving the point + (save-restriction + (widen) + (goto-char marker) + (when (search-forward l nil lkend) + (goto-char (match-beginning 0)) + (org-open-at-point))) ;; This is an internal link, widen the buffer + ;; FIXME: use `org-switch-to-buffer-other-window'? (switch-to-buffer-other-window buffer) (widen) (goto-char marker) @@ -8981,7 +9142,6 @@ fold drawers." (narrow-to-region (org-entry-beginning-position) (org-entry-end-position)) (org-show-all '(drawers)))) - (when arg ) (setq org-agenda-show-window (selected-window))) (select-window win))) @@ -9002,8 +9162,7 @@ The prefix arg selects the amount of information to display: 1 just show the entry according to defaults. 2 show the children view 3 show the subtree view -4 show the entire subtree and any LOGBOOK drawers -5 show the entire subtree and any drawers +4 show the entire subtree and any drawers With prefix argument FULL-ENTRY, make the entire entry visible if it was hidden in the outline." (interactive "p") @@ -9033,13 +9192,7 @@ if it was hidden in the outline." (org-back-to-heading) (run-hook-with-args 'org-cycle-hook 'subtree)) (message "Remote: SUBTREE")) - ((= more 4) - (outline-show-subtree) - (save-excursion - (org-back-to-heading) - (org-cycle-hide-drawers 'subtree '("LOGBOOK"))) - (message "Remote: SUBTREE AND LOGBOOK")) - ((> more 4) + ((> more 3) (outline-show-subtree) (message "Remote: SUBTREE AND ALL DRAWERS"))) (select-window win))) @@ -9154,44 +9307,46 @@ This changes the line at point, all other lines in the agenda referring to the same tree node, and the headline of the tree node in the Org file." (interactive "P") (org-agenda-check-no-diary) - (let* ((col (current-column)) - (marker (or (org-get-at-bol 'org-marker) - (org-agenda-error))) - (buffer (marker-buffer marker)) - (pos (marker-position marker)) - (hdmarker (org-get-at-bol 'org-hd-marker)) - (todayp (org-agenda-today-p (org-get-at-bol 'day))) - (inhibit-read-only t) - org-loop-over-headlines-in-active-region - org-agenda-headline-snapshot-before-repeat newhead just-one) - (org-with-remote-undo buffer - (with-current-buffer buffer - (widen) - (goto-char pos) - (org-show-context 'agenda) - (let ((current-prefix-arg arg)) - (call-interactively 'org-todo)) - (and (bolp) (forward-char 1)) - (setq newhead (org-get-heading)) - (when (and (bound-and-true-p - org-agenda-headline-snapshot-before-repeat) - (not (equal org-agenda-headline-snapshot-before-repeat - newhead)) - todayp) - (setq newhead org-agenda-headline-snapshot-before-repeat - just-one t)) - (save-excursion - (org-back-to-heading) - (move-marker org-last-heading-marker (point)))) - (beginning-of-line 1) - (save-window-excursion - (org-agenda-change-all-lines newhead hdmarker 'fixface just-one)) - (when (bound-and-true-p org-clock-out-when-done) - (string-match (concat "^" (regexp-opt org-done-keywords-for-agenda)) - newhead) - (org-agenda-unmark-clocking-task)) - (org-move-to-column col) - (org-agenda-mark-clocking-task)))) + (org-agenda-maybe-loop + #'org-agenda-todo arg nil nil + (let* ((col (current-column)) + (marker (or (org-get-at-bol 'org-marker) + (org-agenda-error))) + (buffer (marker-buffer marker)) + (pos (marker-position marker)) + (hdmarker (org-get-at-bol 'org-hd-marker)) + (todayp (org-agenda-today-p (org-get-at-bol 'day))) + (inhibit-read-only t) + org-loop-over-headlines-in-active-region + org-agenda-headline-snapshot-before-repeat newhead just-one) + (org-with-remote-undo buffer + (with-current-buffer buffer + (widen) + (goto-char pos) + (org-show-context 'agenda) + (let ((current-prefix-arg arg)) + (call-interactively 'org-todo)) + (and (bolp) (forward-char 1)) + (setq newhead (org-get-heading)) + (when (and (bound-and-true-p + org-agenda-headline-snapshot-before-repeat) + (not (equal org-agenda-headline-snapshot-before-repeat + newhead)) + todayp) + (setq newhead org-agenda-headline-snapshot-before-repeat + just-one t)) + (save-excursion + (org-back-to-heading) + (move-marker org-last-heading-marker (point)))) + (beginning-of-line 1) + (save-window-excursion + (org-agenda-change-all-lines newhead hdmarker 'fixface just-one)) + (when (bound-and-true-p org-clock-out-when-done) + (string-match (concat "^" (regexp-opt org-done-keywords-for-agenda)) + newhead) + (org-agenda-unmark-clocking-task)) + (org-move-to-column col) + (org-agenda-mark-clocking-task))))) (defun org-agenda-add-note (&optional arg) "Add a time-stamped note to the entry at point." @@ -9330,9 +9485,9 @@ the same tree node, and the headline of the tree node in the Org file. Called with a universal prefix arg, show the priority instead of setting it." (interactive "P") (if (equal force-direction '(4)) - (org-show-priority) - (unless org-enable-priority-commands - (error "Priority commands are disabled")) + (org-priority-show) + (unless org-priority-enable-commands + (user-error "Priority commands are disabled")) (org-agenda-check-no-diary) (let* ((col (current-column)) (marker (or (org-get-at-bol 'org-marker) @@ -9383,59 +9538,65 @@ Called with a universal prefix arg, show the priority instead of setting it." "Set a property for the current headline." (interactive) (org-agenda-check-no-diary) - (let* ((hdmarker (or (org-get-at-bol 'org-hd-marker) - (org-agenda-error))) - (buffer (marker-buffer hdmarker)) - (pos (marker-position hdmarker)) - (inhibit-read-only t) - newhead) - (org-with-remote-undo buffer - (with-current-buffer buffer - (widen) - (goto-char pos) - (org-show-context 'agenda) - (call-interactively 'org-set-property))))) + (org-agenda-maybe-loop + #'org-agenda-set-property nil nil nil + (let* ((hdmarker (or (org-get-at-bol 'org-hd-marker) + (org-agenda-error))) + (buffer (marker-buffer hdmarker)) + (pos (marker-position hdmarker)) + (inhibit-read-only t) + newhead) + (org-with-remote-undo buffer + (with-current-buffer buffer + (widen) + (goto-char pos) + (org-show-context 'agenda) + (call-interactively 'org-set-property)))))) (defun org-agenda-set-effort () "Set the effort property for the current headline." (interactive) (org-agenda-check-no-diary) - (let* ((hdmarker (or (org-get-at-bol 'org-hd-marker) - (org-agenda-error))) - (buffer (marker-buffer hdmarker)) - (pos (marker-position hdmarker)) - (inhibit-read-only t) - newhead) - (org-with-remote-undo buffer - (with-current-buffer buffer - (widen) - (goto-char pos) - (org-show-context 'agenda) - (call-interactively 'org-set-effort) - (end-of-line 1) - (setq newhead (org-get-heading))) - (org-agenda-change-all-lines newhead hdmarker)))) + (org-agenda-maybe-loop + #'org-agenda-set-effort nil nil nil + (let* ((hdmarker (or (org-get-at-bol 'org-hd-marker) + (org-agenda-error))) + (buffer (marker-buffer hdmarker)) + (pos (marker-position hdmarker)) + (inhibit-read-only t) + newhead) + (org-with-remote-undo buffer + (with-current-buffer buffer + (widen) + (goto-char pos) + (org-show-context 'agenda) + (call-interactively 'org-set-effort) + (end-of-line 1) + (setq newhead (org-get-heading))) + (org-agenda-change-all-lines newhead hdmarker))))) (defun org-agenda-toggle-archive-tag () "Toggle the archive tag for the current entry." (interactive) (org-agenda-check-no-diary) - (let* ((hdmarker (or (org-get-at-bol 'org-hd-marker) - (org-agenda-error))) - (buffer (marker-buffer hdmarker)) - (pos (marker-position hdmarker)) - (inhibit-read-only t) - newhead) - (org-with-remote-undo buffer - (with-current-buffer buffer - (widen) - (goto-char pos) - (org-show-context 'agenda) - (call-interactively 'org-toggle-archive-tag) - (end-of-line 1) - (setq newhead (org-get-heading))) - (org-agenda-change-all-lines newhead hdmarker) - (beginning-of-line 1)))) + (org-agenda-maybe-loop + #'org-agenda-toggle-archive-tag nil nil nil + (let* ((hdmarker (or (org-get-at-bol 'org-hd-marker) + (org-agenda-error))) + (buffer (marker-buffer hdmarker)) + (pos (marker-position hdmarker)) + (inhibit-read-only t) + newhead) + (org-with-remote-undo buffer + (with-current-buffer buffer + (widen) + (goto-char pos) + (org-show-context 'agenda) + (call-interactively 'org-toggle-archive-tag) + (end-of-line 1) + (setq newhead (org-get-heading))) + (org-agenda-change-all-lines newhead hdmarker) + (beginning-of-line 1))))) (defun org-agenda-do-date-later (arg) (interactive "P") @@ -9541,8 +9702,11 @@ Called with a universal prefix arg, show the priority instead of setting it." (goto-char (point-max)) (while (not (bobp)) (when (equal marker (org-get-at-bol 'org-marker)) - (remove-text-properties (point-at-bol) (point-at-eol) '(display nil)) - (org-move-to-column (- (window-width) (length stamp)) t) + (remove-text-properties (line-beginning-position) + (line-end-position) + '(display nil)) + (org-move-to-column + (- (/ (window-width nil t) (window-font-width)) (length stamp)) t) (add-text-properties (1- (point)) (point-at-eol) (list 'display (org-add-props stamp nil @@ -9557,18 +9721,20 @@ be used to request time specification in the time stamp." (interactive "P") (org-agenda-check-type t 'agenda) (org-agenda-check-no-diary) - (let* ((marker (or (org-get-at-bol 'org-marker) - (org-agenda-error))) - (buffer (marker-buffer marker)) - (pos (marker-position marker))) - (org-with-remote-undo buffer - (with-current-buffer buffer - (widen) - (goto-char pos) - (unless (org-at-timestamp-p 'lax) (error "Cannot find time stamp")) - (org-time-stamp arg (equal (char-after (match-beginning 0)) ?\[))) - (org-agenda-show-new-time marker org-last-changed-timestamp)) - (message "Time stamp changed to %s" org-last-changed-timestamp))) + (org-agenda-maybe-loop + #'org-agenda-date-prompt arg t nil + (let* ((marker (or (org-get-at-bol 'org-marker) + (org-agenda-error))) + (buffer (marker-buffer marker)) + (pos (marker-position marker))) + (org-with-remote-undo buffer + (with-current-buffer buffer + (widen) + (goto-char pos) + (unless (org-at-timestamp-p 'lax) (error "Cannot find time stamp")) + (org-time-stamp arg (equal (char-after (match-beginning 0)) ?\[))) + (org-agenda-show-new-time marker org-last-changed-timestamp)) + (message "Time stamp changed to %s" org-last-changed-timestamp)))) (defun org-agenda-schedule (arg &optional time) "Schedule the item at point. @@ -9576,20 +9742,22 @@ ARG is passed through to `org-schedule'." (interactive "P") (org-agenda-check-type t 'agenda 'todo 'tags 'search) (org-agenda-check-no-diary) - (let* ((marker (or (org-get-at-bol 'org-marker) - (org-agenda-error))) - (type (marker-insertion-type marker)) - (buffer (marker-buffer marker)) - (pos (marker-position marker)) - ts) - (set-marker-insertion-type marker t) - (org-with-remote-undo buffer - (with-current-buffer buffer - (widen) - (goto-char pos) - (setq ts (org-schedule arg time))) - (org-agenda-show-new-time marker ts " S")) - (message "%s" ts))) + (org-agenda-maybe-loop + #'org-agenda-schedule arg t nil + (let* ((marker (or (org-get-at-bol 'org-marker) + (org-agenda-error))) + (type (marker-insertion-type marker)) + (buffer (marker-buffer marker)) + (pos (marker-position marker)) + ts) + (set-marker-insertion-type marker t) + (org-with-remote-undo buffer + (with-current-buffer buffer + (widen) + (goto-char pos) + (setq ts (org-schedule arg time))) + (org-agenda-show-new-time marker ts " S")) + (message "%s" ts)))) (defun org-agenda-deadline (arg &optional time) "Schedule the item at point. @@ -9597,18 +9765,20 @@ ARG is passed through to `org-deadline'." (interactive "P") (org-agenda-check-type t 'agenda 'todo 'tags 'search) (org-agenda-check-no-diary) - (let* ((marker (or (org-get-at-bol 'org-marker) - (org-agenda-error))) - (buffer (marker-buffer marker)) - (pos (marker-position marker)) - ts) - (org-with-remote-undo buffer - (with-current-buffer buffer - (widen) - (goto-char pos) - (setq ts (org-deadline arg time))) - (org-agenda-show-new-time marker ts " D")) - (message "%s" ts))) + (org-agenda-maybe-loop + #'org-agenda-deadline arg t nil + (let* ((marker (or (org-get-at-bol 'org-marker) + (org-agenda-error))) + (buffer (marker-buffer marker)) + (pos (marker-position marker)) + ts) + (org-with-remote-undo buffer + (with-current-buffer buffer + (widen) + (goto-char pos) + (setq ts (org-deadline arg time))) + (org-agenda-show-new-time marker ts " D")) + (message "%s" ts)))) (defun org-agenda-clock-in (&optional arg) "Start the clock on the currently selected item." @@ -9636,7 +9806,7 @@ ARG is passed through to `org-deadline'." "Stop the currently running clock." (interactive) (unless (marker-buffer org-clock-marker) - (error "No running clock")) + (user-error "No running clock")) (let ((marker (make-marker)) (col (current-column)) newhead) (org-with-remote-undo (marker-buffer org-clock-marker) (with-current-buffer (marker-buffer org-clock-marker) @@ -9792,13 +9962,13 @@ the resulting entry will not be shown. When TEXT is empty, switch to (org-agenda-insert-diary-make-new-entry text)) (org-insert-time-stamp (org-time-from-absolute (calendar-absolute-from-gregorian d1)) - nil nil nil nil time2)) + nil nil nil nil time2)) (end-of-line 0)) ((block) ;; Wrap this in (strictly unnecessary) parens because ;; otherwise the indentation gets confused by the ;; special meaning of 'block (when (> (calendar-absolute-from-gregorian d1) - (calendar-absolute-from-gregorian d2)) + (calendar-absolute-from-gregorian d2)) (setq d1 (prog1 d2 (setq d2 d1)))) (if (eq org-agenda-insert-diary-strategy 'top-level) (org-agenda-insert-diary-as-top-level text) @@ -10062,13 +10232,13 @@ When ARG is greater than one mark ARG lines." (goto-char (point-min)) (goto-char (next-single-property-change (point) 'org-hd-marker)) (while (and (re-search-forward regexp nil t) - (setq txt-at-point (get-text-property (point) 'txt))) + (setq txt-at-point + (get-text-property (match-beginning 0) 'txt))) (if (get-char-property (point) 'invisible) (beginning-of-line 2) - (when (string-match regexp txt-at-point) + (when (string-match-p regexp txt-at-point) (setq entries-marked (1+ entries-marked)) (call-interactively 'org-agenda-bulk-mark))))) - (unless entries-marked (message "No entry matching this regexp.")))) @@ -10138,6 +10308,33 @@ bulk action." :version "24.1" :type 'boolean) +(defcustom org-agenda-loop-over-headlines-in-active-region t + "Shall some commands act upon headlines in the active region? + +When set to t, some commands will be performed in all headlines +within the active region. + +When set to `start-level', some commands will be performed in all +headlines within the active region, provided that these headlines +are of the same level than the first one. + +When set to a regular expression, those commands will be +performed on the matching headlines within the active region. + +The list of commands is: `org-agenda-schedule', +`org-agenda-deadline', `org-agenda-date-prompt', +`org-agenda-todo', `org-agenda-archive*', `org-agenda-kill'. + +See `org-loop-over-headlines-in-active-region' for the equivalent +option for Org buffers." + :type '(choice (const :tag "Don't loop" nil) + (const :tag "All headlines in active region" t) + (const :tag "In active region, headlines at the same level than the first one" start-level) + (regexp :tag "Regular expression matcher")) + :version "27.1" + :package-version '(Org . "9.4") + :group 'org-agenda) + (defun org-agenda-bulk-action (&optional arg) "Execute an remote-editing action on all marked entries. The prefix arg is passed through to the command if possible." @@ -10547,6 +10744,15 @@ when defining today." (org-extend-today-until (1+ hour))) (org-agenda-todo arg))) +(defun org-agenda-ctrl-c-ctrl-c () + "Set tags in agenda buffer." + (interactive) + (org-agenda-set-tags)) + (provide 'org-agenda) +;; Local variables: +;; generated-autoload-file: "org-loaddefs.el" +;; End: + ;;; org-agenda.el ends here diff --git a/lisp/org/org-archive.el b/lisp/org/org-archive.el index b33025be0f8..4a0de3cb5a6 100644 --- a/lisp/org/org-archive.el +++ b/lisp/org/org-archive.el @@ -24,7 +24,7 @@ ;; ;;; Commentary: -;; This file contains the face definitions for Org. +;; This file contains the archive functionality for Org. ;;; Code: @@ -91,6 +91,25 @@ When a string, a %s formatter will be replaced by the file name." (const :tag "When archiving a subtree to the same file" infile) (const :tag "Always" t))) +(defcustom org-archive-subtree-save-file-p 'from-org + "Conditionally save the archive file after archiving a subtree. +This variable can be any of the following symbols: + +t saves in all cases. +`from-org' prevents saving from an agenda-view. +`from-agenda' saves only when the archive is initiated from an agenda-view. +nil prevents saving in all cases. + +Note that, regardless of this value, the archive buffer is never +saved when archiving into a location in the current buffer." + :group 'org-archive + :package-version '(Org . "9.4") + :type '(choice + (const :tag "Save archive buffer" t) + (const :tag "Save when archiving from agenda" from-agenda) + (const :tag "Save when archiving from an Org buffer" from-org) + (const :tag "Do not save"))) + (defcustom org-archive-save-context-info '(time file olpath category todo itags) "Parts of context info that should be stored as properties when archiving. When a subtree is moved to an archive file, it loses information given by @@ -230,12 +249,20 @@ direct children of this heading." ((find-buffer-visiting afile)) ((find-file-noselect afile)) (t (error "Cannot access file \"%s\"" afile)))) + (org-odd-levels-only + (if (local-variable-p 'org-odd-levels-only (current-buffer)) + org-odd-levels-only + tr-org-odd-levels-only)) level datetree-date datetree-subheading-p) - (when (string-match "\\`datetree/" heading) - ;; Replace with ***, to represent the 3 levels of headings the - ;; datetree has. - (setq heading (replace-regexp-in-string "\\`datetree/" "***" heading)) - (setq datetree-subheading-p (> (length heading) 3)) + (when (string-match "\\`datetree/\\(\\**\\)" heading) + ;; "datetree/" corresponds to 3 levels of headings. + (let ((nsub (length (match-string 1 heading)))) + (setq heading (concat (make-string + (+ (if org-odd-levels-only 5 3) + (* (org-level-increment) nsub)) + ?*) + (substring heading (match-end 0)))) + (setq datetree-subheading-p (> nsub 0))) (setq datetree-date (org-date-to-gregorian (or (org-entry-get nil "CLOSED" t) time)))) (if (and (> (length heading) 0) @@ -290,11 +317,7 @@ direct children of this heading." (org-todo-kwd-alist tr-org-todo-kwd-alist) (org-done-keywords tr-org-done-keywords) (org-todo-regexp tr-org-todo-regexp) - (org-todo-line-regexp tr-org-todo-line-regexp) - (org-odd-levels-only - (if (local-variable-p 'org-odd-levels-only (current-buffer)) - org-odd-levels-only - tr-org-odd-levels-only))) + (org-todo-line-regexp tr-org-todo-line-regexp)) (goto-char (point-min)) (org-show-all '(headings blocks)) (if (and heading (not (and datetree-date (not datetree-subheading-p)))) @@ -361,6 +384,15 @@ direct children of this heading." (point) (concat "ARCHIVE_" (upcase (symbol-name item))) value)))) + ;; Save the buffer, if it is not the same buffer and + ;; depending on `org-archive-subtree-save-file-p'. + (unless (eq this-buffer buffer) + (when (or (eq org-archive-subtree-save-file-p t) + (eq org-archive-subtree-save-file-p + (if (boundp 'org-archive-from-agenda) + 'from-agenda + 'from-org))) + (save-buffer))) (widen)))) ;; Here we are back in the original buffer. Everything seems ;; to have worked. So now run hooks, cut the tree and finish diff --git a/lisp/org/org-attach.el b/lisp/org/org-attach.el index 1ed305c9ff3..e6aa97e0080 100644 --- a/lisp/org/org-attach.el +++ b/lisp/org/org-attach.el @@ -4,7 +4,6 @@ ;; Author: John Wiegley ;; Keywords: org data attachment - ;; This file is part of GNU Emacs. ;; ;; GNU Emacs is free software: you can redistribute it and/or modify @@ -41,6 +40,8 @@ (require 'org-id) (declare-function dired-dwim-target-directory "dired-aux") +(declare-function org-element-property "org-element" (property element)) +(declare-function org-element-type "org-element" (element)) (defgroup org-attach nil "Options concerning attachments in Org mode." @@ -129,8 +130,7 @@ Selective means to respect the inheritance setting in :type '(choice (const :tag "Don't use inheritance" nil) (const :tag "Inherit parent node attachments" t) - (const :tag "Respect org-use-property-inheritance" selective)) - :type 'boolean) + (const :tag "Respect org-use-property-inheritance" selective))) (defcustom org-attach-store-link-p nil "Non-nil means store a link to a file when attaching it." @@ -139,7 +139,8 @@ Selective means to respect the inheritance setting in :type '(choice (const :tag "Don't store link" nil) (const :tag "Link to origin location" t) - (const :tag "Link to the attach-dir location" attached))) + (const :tag "Attachment link to the attach-dir location" attached) + (const :tag "File link to the attach-dir location" file))) (defcustom org-attach-archive-delete nil "Non-nil means attachments are deleted upon archiving a subtree. @@ -254,16 +255,16 @@ Shows a list of commands and prompts for another key to execute a command." (get-text-property (point) 'org-marker))) (unless marker (error "No item in current line"))) - (save-excursion - (when marker - (set-buffer (marker-buffer marker)) - (goto-char marker)) - (org-back-to-heading t) + (org-with-point-at marker + (org-back-to-heading-or-point-min t) (save-excursion (save-window-excursion (unless org-attach-expert - (with-output-to-temp-buffer "*Org Attach*" - (princ + (org-switch-to-buffer-other-window "*Org Attach*") + (erase-buffer) + (setq cursor-type nil + header-line-format "Use C-v, M-v, C-n or C-p to navigate.") + (insert (concat "Attachment folder:\n" (or dir "Can't find an existing attachment-folder") @@ -286,11 +287,14 @@ Shows a list of commands and prompts for another key to execute a command." "Invalid `org-attach-commands' item: %S" entry)))) org-attach-commands - "\n")))))) + "\n"))))) (org-fit-window-to-buffer (get-buffer-window "*Org Attach*")) - (message "Select command: [%s]" - (concat (mapcar #'caar org-attach-commands))) - (setq c (read-char-exclusive)) + (let ((msg (format "Select command: [%s]" + (concat (mapcar #'caar org-attach-commands))))) + (message msg) + (while (and (setq c (read-char-exclusive)) + (memq c '(14 16 22 134217846))) + (org-scroll c t))) (and (get-buffer "*Org Attach*") (kill-buffer "*Org Attach*")))) (let ((command (cl-some (lambda (entry) (and (memq c (nth 0 entry)) (nth 1 entry))) @@ -457,14 +461,6 @@ DIR-property exists (that is different from the unset one)." "Turn the autotag off." (org-attach-tag 'off)) -(defun org-attach-store-link (file) - "Add a link to `org-stored-link' when attaching a file. -Only do this when `org-attach-store-link-p' is non-nil." - (setq org-stored-links - (cons (list (org-attach-expand-link file) - (file-name-nondirectory file)) - org-stored-links))) - (defun org-attach-url (url) (interactive "MURL of the file to attach: \n") (let ((org-attach-method 'url)) @@ -491,7 +487,7 @@ METHOD may be `cp', `mv', `ln', `lns' or `url' default taken from `org-attach-method'." (interactive (list - (read-file-name "File to keep as an attachment:" + (read-file-name "File to keep as an attachment: " (or (progn (require 'dired-aux) (dired-dwim-target-directory)) @@ -501,22 +497,30 @@ METHOD may be `cp', `mv', `ln', `lns' or `url' default taken from (setq method (or method org-attach-method)) (let ((basename (file-name-nondirectory file))) (let* ((attach-dir (org-attach-dir 'get-create)) - (fname (expand-file-name basename attach-dir))) + (attach-file (expand-file-name basename attach-dir))) (cond - ((eq method 'mv) (rename-file file fname)) - ((eq method 'cp) (copy-file file fname)) - ((eq method 'ln) (add-name-to-file file fname)) - ((eq method 'lns) (make-symbolic-link file fname)) - ((eq method 'url) (url-copy-file file fname))) + ((eq method 'mv) (rename-file file attach-file)) + ((eq method 'cp) (copy-file file attach-file)) + ((eq method 'ln) (add-name-to-file file attach-file)) + ((eq method 'lns) (make-symbolic-link file attach-file)) + ((eq method 'url) (url-copy-file file attach-file))) (run-hook-with-args 'org-attach-after-change-hook attach-dir) (org-attach-tag) (cond ((eq org-attach-store-link-p 'attached) - (org-attach-store-link fname)) + (push (list (concat "attachment:" (file-name-nondirectory attach-file)) + (file-name-nondirectory attach-file)) + org-stored-links)) ((eq org-attach-store-link-p t) - (org-attach-store-link file))) + (push (list (concat "file:" file) + (file-name-nondirectory file)) + org-stored-links)) + ((eq org-attach-store-link-p 'file) + (push (list (concat "file:" attach-file) + (file-name-nondirectory attach-file)) + org-stored-links))) (if visit-dir (dired attach-dir) - (message "File %S is now an attachment." basename))))) + (message "File %S is now an attachment" basename))))) (defun org-attach-attach-cp () "Attach a file by copying it." @@ -569,13 +573,18 @@ The attachment is created as an Emacs buffer." (defun org-attach-delete-all (&optional force) "Delete all attachments from the current outline node. This actually deletes the entire attachment directory. -A safer way is to open the directory in dired and delete from there." +A safer way is to open the directory in dired and delete from there. + +With prefix argument FORCE, directory will be recursively deleted +with no prompts." (interactive "P") (let ((attach-dir (org-attach-dir))) (when (and attach-dir (or force (yes-or-no-p "Really remove all attachments of this entry? "))) - (delete-directory attach-dir (yes-or-no-p "Recursive?") t) + (delete-directory attach-dir + (or force (yes-or-no-p "Recursive?")) + t) (message "Attachment directory removed") (run-hook-with-args 'org-attach-after-change-hook attach-dir) (org-attach-untag)))) @@ -642,37 +651,37 @@ See `org-attach-open'." Basically, this adds the path to the attachment directory." (expand-file-name file (org-attach-dir))) -(defun org-attach-expand-link (file) - "Return a file link pointing to the current entry's attachment file FILE. -Basically, this adds the path to the attachment directory, and a \"file:\" -prefix." - (concat "file:" (org-attach-expand file))) +(defun org-attach-expand-links (_) + "Expand links in current buffer. +It is meant to be added to `org-export-before-parsing-hook'." + (save-excursion + (while (re-search-forward "attachment:" nil t) + (let ((link (org-element-context))) + (when (and (eq 'link (org-element-type link)) + (string-equal "attachment" + (org-element-property :type link))) + (let* ((description (and (org-element-property :contents-begin link) + (buffer-substring-no-properties + (org-element-property :contents-begin link) + (org-element-property :contents-end link)))) + (file (org-element-property :path link)) + (new-link (org-link-make-string + (concat "file:" (org-attach-expand file)) + description))) + (goto-char (org-element-property :end link)) + (skip-chars-backward " \t") + (delete-region (org-element-property :begin link) (point)) + (insert new-link))))))) + +(defun org-attach-follow (file arg) + "Open FILE attachment. +See `org-open-file' for details about ARG." + (org-link-open-as-file (org-attach-expand file) arg)) (org-link-set-parameters "attachment" - :follow #'org-attach-open-link - :export #'org-attach-export-link + :follow #'org-attach-follow :complete #'org-attach-complete-link) -(defun org-attach-open-link (link &optional in-emacs) - "Attachment link type LINK is expanded with the attached directory and opened. - -With optional prefix argument IN-EMACS, Emacs will visit the file. -With a double \\[universal-argument] \\[universal-argument] \ -prefix arg, Org tries to avoid opening in Emacs -and to use an external application to visit the file." - (interactive "P") - (let (line search) - (cond - ((string-match "::\\([0-9]+\\)\\'" link) - (setq line (string-to-number (match-string 1 link)) - link (substring link 0 (match-beginning 0)))) - ((string-match "::\\(.+\\)\\'" link) - (setq search (match-string 1 link) - link (substring link 0 (match-beginning 0))))) - (if (string-match "[*?{]" (file-name-nondirectory link)) - (dired (org-attach-expand link)) - (org-open-file (org-attach-expand link) in-emacs line search)))) - (defun org-attach-complete-link () "Advise the user with the available files in the attachment directory." (let ((attach-dir (org-attach-dir))) @@ -691,26 +700,6 @@ and to use an external application to visit the file." (t (concat "attachment:" file)))) (error "No attachment directory exist")))) -(defun org-attach-export-link (link description format) - "Translate attachment LINK from Org mode format to exported FORMAT. -Also includes the DESCRIPTION of the link in the export." - (save-excursion - (let (path desc) - (cond - ((string-match "::\\([0-9]+\\)\\'" link) - (setq link (substring link 0 (match-beginning 0)))) - ((string-match "::\\(.+\\)\\'" link) - (setq link (substring link 0 (match-beginning 0))))) - (setq path (file-relative-name (org-attach-expand link)) - desc (or description link)) - (pcase format - (`html (format "%s" path desc)) - (`latex (format "\\href{%s}{%s}" path desc)) - (`texinfo (format "@uref{%s,%s}" path desc)) - (`ascii (format "%s (%s)" desc path)) - (`md (format "[%s](%s)" desc path)) - (_ path))))) - (defun org-attach-archive-delete-maybe () "Maybe delete subtree attachments when archiving. This function is called by `org-archive-hook'. The option @@ -758,6 +747,7 @@ Idea taken from `gnus-dired-attach'." (add-hook 'org-archive-hook 'org-attach-archive-delete-maybe) +(add-hook 'org-export-before-parsing-hook 'org-attach-expand-links) (provide 'org-attach) diff --git a/lisp/org/org-capture.el b/lisp/org/org-capture.el index 003cbef1fdf..a9a1181935c 100644 --- a/lisp/org/org-capture.el +++ b/lisp/org/org-capture.el @@ -49,11 +49,13 @@ (require 'cl-lib) (require 'org) +(require 'org-refile) (declare-function org-at-encrypted-entry-p "org-crypt" ()) (declare-function org-at-table-p "org-table" (&optional table-type)) (declare-function org-clock-update-mode-line "org-clock" (&optional refresh)) (declare-function org-datetree-find-date-create "org-datetree" (date &optional keep-restriction)) +(declare-function org-datetree-find-month-create (d &optional keep-restriction)) (declare-function org-decrypt-entry "org-crypt" ()) (declare-function org-element-at-point "org-element" ()) (declare-function org-element-lineage "org-element" (datum &optional types with-self)) @@ -68,6 +70,7 @@ (defvar dired-buffers) (defvar org-end-time-was-given) +(defvar org-keyword-properties) (defvar org-remember-default-headline) (defvar org-remember-templates) (defvar org-store-link-plist) @@ -156,14 +159,20 @@ description A short string describing the template, will be shown during type The type of entry. Valid types are: entry an Org node, with a headline. Will be filed as the child of the target entry or as a - top-level entry. + top-level entry. Its default template is: + \"* %?\n %a\" item a plain list item, will be placed in the - first plain list at the target - location. + first plain list at the target location. + Its default template is: + \"- %?\" checkitem a checkbox item. This differs from the plain list item only in so far as it uses a - different default template. + different default template. Its default + template is: + \"- [ ] %?\" table-line a new line in the first table at target location. + Its default template is: + \"| %? |\" plain text to be inserted as it is. target Specification of where the captured item should be placed. @@ -211,9 +220,10 @@ target Specification of where the captured item should be placed. Most general way: write your own function which both visits the file and moves point to the right location -template The template for creating the capture item. If you leave this - empty, an appropriate default template will be used. See below - for more details. Instead of a string, this may also be one of +template The template for creating the capture item. + If it is an empty string or nil, a default template based on + the entry type will be used (see the \"type\" section above). + Instead of a string, this may also be one of: (file \"/path/to/template-file\") (function function-returning-the-template) @@ -236,15 +246,15 @@ properties are: :jump-to-captured When set, jump to the captured entry when finished. - :empty-lines Set this to the number of lines the should be inserted + :empty-lines Set this to the number of lines that should be inserted before and after the new item. Default 0, only common other value is 1. - :empty-lines-before Set this to the number of lines the should be inserted + :empty-lines-before Set this to the number of lines that should be inserted before the new item. Overrides :empty-lines for the number lines inserted before. - :empty-lines-after Set this to the number of lines the should be inserted + :empty-lines-after Set this to the number of lines that should be inserted after the new item. Overrides :empty-lines for the number of lines inserted after. @@ -260,7 +270,9 @@ properties are: :time-prompt Prompt for a date/time to be used for date/week trees and when filling the template. - :tree-type When `week', make a week tree instead of the month tree. + :tree-type When `week', make a week tree instead of the month-day + tree. When `month', make a month tree instead of the + month-day tree. :unnarrowed Do not narrow the target buffer, simply show the full buffer. Default is to narrow it so that you @@ -322,7 +334,7 @@ be replaced with content and expanded: %^L Like %^C, but insert as link. %^{prop}p Prompt the user for a value for property `prop'. %^{prompt} Prompt the user for a string and replace this sequence with it. - A default value and a completion table ca be specified like this: + A default value and a completion table can be specified like this: %^{prompt|default|completion2|completion3|...}. %? After completing the template, position cursor here. %\\1 ... %\\N Insert the text entered at the nth %^{prompt}, where N @@ -625,7 +637,7 @@ of the day at point (if any) or the current HH:MM time." (setq org-overriding-default-time (org-get-cursor-date (equal goto 1)))) (cond - ((equal goto '(4)) (org-capture-goto-target)) + ((equal goto '(4)) (org-capture-goto-target keys)) ((equal goto '(16)) (org-capture-goto-last-stored)) (t (let* ((orig-buf (current-buffer)) @@ -698,21 +710,19 @@ of the day at point (if any) or the current HH:MM time." (defun org-capture-get-template () "Get the template from a file or a function if necessary." - (let ((txt (org-capture-get :template)) file) - (cond - ((and (listp txt) (eq (car txt) 'file)) - (if (file-exists-p - (setq file (expand-file-name (nth 1 txt) org-directory))) - (setq txt (org-file-contents file)) - (setq txt (format "* Template file %s not found" (nth 1 txt))))) - ((and (listp txt) (eq (car txt) 'function)) - (if (fboundp (nth 1 txt)) - (setq txt (funcall (nth 1 txt))) - (setq txt (format "* Template function %s not found" (nth 1 txt))))) - ((not txt) (setq txt "")) - ((stringp txt)) - (t (setq txt "* Invalid capture template"))) - (org-capture-put :template txt))) + (org-capture-put + :template + (pcase (org-capture-get :template) + (`nil "") + ((and (pred stringp) template) template) + (`(file ,file) + (let ((filename (expand-file-name file org-directory))) + (if (file-exists-p filename) (org-file-contents filename) + (format "* Template file %S not found" file)))) + (`(function ,f) + (if (functionp f) (funcall f) + (format "* Template function %S not found" f))) + (_ "* Invalid capture template")))) (defun org-capture-finalize (&optional stay-with-capture) "Finalize the capture process. @@ -727,6 +737,11 @@ captured item after finalizing." (run-hooks 'org-capture-prepare-finalize-hook) + ;; Update `org-capture-plist' with the buffer-local value. Since + ;; captures can be run concurrently, this is to ensure that + ;; `org-capture-after-finalize-hook' accesses the proper plist. + (setq org-capture-plist org-capture-current-plist) + ;; Did we start the clock in this capture buffer? (when (and org-capture-clock-was-started org-clock-marker @@ -996,11 +1011,13 @@ Store them in the capture property list." (org-capture-put-target-region-and-position) (widen) ;; Make a date/week tree entry, with the current date (or - ;; yesterday, if we are extending dates for a couple of hours) + ;; yesterday, if we are extending dates for a couple of + ;; hours) (funcall - (if (eq (org-capture-get :tree-type) 'week) - #'org-datetree-find-iso-week-create - #'org-datetree-find-date-create) + (pcase (org-capture-get :tree-type) + (`week #'org-datetree-find-iso-week-create) + (`month #'org-datetree-find-month-create) + (_ #'org-datetree-find-date-create)) (calendar-gregorian-from-absolute (cond (org-overriding-default-time @@ -1021,7 +1038,7 @@ Store them in the capture property list." (apply #'encode-time 0 0 org-extend-today-until (cl-cdddr (decode-time prompt-time)))) - ((string-match "\\([^ ]+\\)--?[^ ]+[ ]+\\(.*\\)" + ((string-match "\\([^ ]+\\)-[^ ]+[ ]+\\(.*\\)" org-read-date-final-answer) ;; Replace any time range by its start. (apply #'encode-time @@ -1058,7 +1075,7 @@ Store them in the capture property list." (org-capture-put-target-region-and-position) (widen) (goto-char org-clock-hd-marker)) - (error "No running clock that could be used as capture target"))) + (user-error "No running clock that could be used as capture target"))) (target (error "Invalid capture target specification: %S" target))) (org-capture-put :buffer (current-buffer) @@ -1115,8 +1132,8 @@ may have been stored before." (`plain (org-capture-place-plain-text)) (`item (org-capture-place-item)) (`checkitem (org-capture-place-item))) - (org-capture-mode 1) - (setq-local org-capture-current-plist org-capture-plist)) + (setq-local org-capture-current-plist org-capture-plist) + (org-capture-mode 1)) (defun org-capture-place-entry () "Place the template as a new Org entry." @@ -1129,7 +1146,14 @@ may have been stored before." (when exact-position (goto-char exact-position)) (cond ;; Force insertion at point. - ((org-capture-get :insert-here) nil) + (insert-here? + ;; FIXME: level should probably set directly within (let ...). + (setq level (org-get-valid-level + (if (or (org-at-heading-p) + (ignore-errors + (save-excursion (org-back-to-heading t)))) + (org-outline-level) + 1)))) ;; Insert as a child of the current entry. ((org-capture-get :target-entry-p) (setq level (org-get-valid-level @@ -1150,14 +1174,11 @@ may have been stored before." (when insert-here? (narrow-to-region beg beg)) (org-paste-subtree level template 'for-yank)) (org-capture-position-for-last-stored beg) - (let ((end (if (org-at-heading-p) (line-end-position 0) (point)))) - (org-capture-empty-lines-after) - (unless (org-at-heading-p) (outline-next-heading)) - (org-capture-mark-kill-region origin (point)) - (org-capture-narrow beg end) - (when (or (search-backward "%?" beg t) - (search-forward "%?" end t)) - (replace-match ""))))))) + (org-capture-empty-lines-after) + (unless (org-at-heading-p) (outline-next-heading)) + (org-capture-mark-kill-region origin (point)) + (org-capture-narrow beg (if (eobp) (point) (1- (point)))) + (org-capture--position-cursor beg (point)))))) (defun org-capture-place-item () "Place the template as a new plain list item." @@ -1269,9 +1290,7 @@ may have been stored before." ;; not narrow at the beginning of the next line, possibly ;; altering its structure (e.g., when it is a headline). (org-capture-narrow beg (1- end)) - (when (or (search-backward "%?" beg t) - (search-forward "%?" end t)) - (replace-match "")))))) + (org-capture--position-cursor beg end))))) (defun org-capture-place-table-line () "Place the template as a table line." @@ -1353,9 +1372,7 @@ may have been stored before." ;; TEXT is guaranteed to end with a newline character. Ignore ;; it when narrowing so as to not alter data on the next line. (org-capture-narrow beg (1- end)) - (when (or (search-backward "%?" beg t) - (search-forward "%?" end t)) - (replace-match "")))))) + (org-capture--position-cursor beg (1- end)))))) (defun org-capture-place-plain-text () "Place the template plainly. @@ -1390,9 +1407,7 @@ Of course, if exact position has been required, just put it there." (org-capture-empty-lines-after) (org-capture-mark-kill-region origin (point)) (org-capture-narrow beg end) - (when (or (search-backward "%?" beg t) - (search-forward "%?" end t)) - (replace-match "")))))) + (org-capture--position-cursor beg end))))) (defun org-capture-mark-kill-region (beg end) "Mark the region that will have to be killed when aborting capture." @@ -1438,8 +1453,15 @@ Of course, if exact position has been required, just put it there." (defun org-capture-narrow (beg end) "Narrow, unless configuration says not to narrow." (unless (org-capture-get :unnarrowed) - (narrow-to-region beg end) - (goto-char beg))) + (narrow-to-region beg end))) + +(defun org-capture--position-cursor (beg end) + "Move point to first \"%?\" location or at start of template. +BEG and END are buffer positions at the beginning and end position +of the template." + (goto-char beg) + (when (search-forward "%?" end t) + (replace-match ""))) (defun org-capture-empty-lines-before (&optional n) "Set the correct number of empty lines before the insertion point. @@ -1736,11 +1758,11 @@ The template may still contain \"%?\" for cursor positioning." (_ (error "Invalid `org-capture--clipboards' value: %S" org-capture--clipboards))))) ("p" - ;; We remove file properties inherited from + ;; We remove keyword properties inherited from ;; target buffer so `org-read-property-value' has ;; a chance to find allowed values in sub-trees ;; from the target buffer. - (setq-local org-file-properties nil) + (setq-local org-keyword-properties nil) (let* ((origin (set-marker (make-marker) (org-capture-get :pos) (org-capture-get :buffer))) @@ -1925,4 +1947,8 @@ Assume sexps have been marked with (provide 'org-capture) +;; Local variables: +;; generated-autoload-file: "org-loaddefs.el" +;; End: + ;;; org-capture.el ends here diff --git a/lisp/org/org-clock.el b/lisp/org/org-clock.el index 06df2d49719..9efd99be826 100644 --- a/lisp/org/org-clock.el +++ b/lisp/org/org-clock.el @@ -35,11 +35,17 @@ (declare-function notifications-notify "notifications" (&rest params)) (declare-function org-element-property "org-element" (property element)) (declare-function org-element-type "org-element" (element)) +(declare-function org-inlinetask-at-task-p "org-inlinetask" ()) +(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-link-display-format "ol" (s)) (declare-function org-link-heading-search-string "ol" (&optional string)) (declare-function org-link-make-string "ol" (link &optional description)) (declare-function org-table-goto-line "org-table" (n)) (declare-function org-dynamic-block-define "org" (type func)) +(declare-function w32-notification-notify "w32fns.c" (&rest params)) +(declare-function w32-notification-close "w32fns.c" (&rest params)) (defvar org-frame-title-format-backup nil) (defvar org-state) @@ -273,6 +279,15 @@ also using the face `org-mode-line-clock-overrun'." (const :tag "Just mark the time string" nil) (string :tag "Text to prepend"))) +(defcustom org-show-notification-timeout 3 + "Number of seconds to wait before closing Org notifications. +This is applied to notifications sent with `notifications-notify' +and `w32-notification-notify' only, not other mechanisms possibly +set through `org-show-notification-handler'." + :group 'org-clock + :package-version '(Org . "9.4") + :type 'integer) + (defcustom org-show-notification-handler nil "Function or program to send notification with. The function or program will be called with the notification @@ -457,6 +472,19 @@ Valid values are: `today', `yesterday', `thisweek', `lastweek', (const :tag "Select range interactively" interactive)) :safe #'symbolp) +(defcustom org-clock-auto-clockout-timer nil + "Timer for auto clocking out when Emacs is idle. +When set to a number, auto clock out the currently clocked in +task after this number of seconds of idle time. + +This is only effective when `org-clock-auto-clockout-insinuate' +is added to the user configuration." + :group 'org-clock + :package-version '(Org . "9.4") + :type '(choice + (integer :tag "Clock out after Emacs is idle for X seconds") + (const :tag "Never auto clock out" nil))) + (defvar org-clock-in-prepare-hook nil "Hook run when preparing the clock. This hook is run before anything happens to the task that @@ -698,7 +726,8 @@ If not, show simply the clocked time like 01:50." (save-excursion (let ((end (save-excursion (org-end-of-subtree)))) (when (re-search-forward (concat org-clock-string - ".*\\]--\\(\\[[^]]+\\]\\)") end t) + ".*\\]--\\(\\[[^]]+\\]\\)") + end t) (org-time-string-to-time (match-string 1)))))) (defun org-clock-update-mode-line (&optional refresh) @@ -725,7 +754,8 @@ menu\nmouse-2 will jump to task")) (setq org-mode-line-string (concat (propertize org-clock-task-overrun-text - 'face 'org-mode-line-clock-overrun) org-mode-line-string))) + 'face 'org-mode-line-clock-overrun) + org-mode-line-string))) (force-mode-line-update)) (defun org-clock-get-clocked-time () @@ -808,15 +838,26 @@ If PLAY-SOUND is non-nil, it overrides `org-clock-sound'." "Show notification. Use `org-show-notification-handler' if defined, use libnotify if available, or fall back on a message." + (ignore-errors (require 'notifications)) (cond ((functionp org-show-notification-handler) (funcall org-show-notification-handler notification)) ((stringp org-show-notification-handler) (start-process "emacs-timer-notification" nil org-show-notification-handler notification)) + ((fboundp 'w32-notification-notify) + (let ((id (w32-notification-notify + :title "Org mode message" + :body notification + :urgency 'low))) + (run-with-timer + org-show-notification-timeout + nil + (lambda () (w32-notification-close id))))) ((fboundp 'notifications-notify) (notifications-notify :title "Org mode message" :body notification + :timeout (* org-show-notification-timeout 1000) ;; FIXME how to link to the Org icon? ;; :app-icon "~/.emacs.d/icons/mail.png" :urgency 'low)) @@ -859,7 +900,8 @@ If CLOCK-SOUND is non-nil, it overrides `org-clock-sound'." (goto-char (point-min)) (while (re-search-forward org-clock-re nil t) (push (cons (copy-marker (match-end 1) t) - (org-time-string-to-time (match-string 1))) clocks)))) + (org-time-string-to-time (match-string 1))) + clocks)))) clocks)) (defsubst org-is-active-clock (clock) @@ -983,7 +1025,7 @@ CLOCK is a cons cell of the form (MARKER START-TIME)." (let ((element (org-element-at-point))) (when (eq (org-element-type element) 'drawer) (when (> (org-element-property :end element) (car clock)) - (org-flag-drawer nil element)) + (org-hide-drawer-toggle 'off nil element)) (throw 'exit nil))))))))))) (defun org-clock-resolve (clock &optional prompt-fn last-valid fail-quietly) @@ -1022,6 +1064,9 @@ k/K Keep X minutes of the idle time (default is all). If this that many minutes after the time that idling began, and then clocked back in at the present time. +t/T Like `k', but will ask you to specify a time (when you got + distracted away), instead of a number of minutes. + g/G Indicate that you \"got back\" X minutes ago. This is quite different from `k': it clocks you out from the beginning of the idle period and clock you back in X minutes ago. @@ -1041,19 +1086,24 @@ to be CLOCKED OUT.")))) (while (or (null char-pressed) (and (not (memq char-pressed '(?k ?K ?g ?G ?s ?S ?C - ?j ?J ?i ?q))) + ?j ?J ?i ?q ?t ?T))) (or (ding) t))) (setq char-pressed (read-char (concat (funcall prompt-fn clock) - " [jkKgGSscCiq]? ") + " [jkKtTgGSscCiq]? ") nil 45))) (and (not (memq char-pressed '(?i ?q))) char-pressed))))) (default (floor (org-time-convert-to-integer (org-time-since last-valid)) 60)) (keep - (and (memq ch '(?k ?K)) - (read-number "Keep how many minutes? " default))) + (or (and (memq ch '(?k ?K)) + (read-number "Keep how many minutes? " default)) + (and (memq ch '(?t ?T)) + (floor + (/ (float-time + (org-time-subtract (org-read-date t t) last-valid)) + 60))))) (gotback (and (memq ch '(?g ?G)) (read-number "Got back how many minutes ago? " default))) @@ -1068,7 +1118,7 @@ to be CLOCKED OUT.")))) (org-clock-resolve-clock clock 'now nil t nil fail-quietly)) (org-clock-jump-to-current-clock clock)) ((or (null ch) - (not (memq ch '(?k ?K ?g ?G ?s ?S ?C)))) + (not (memq ch '(?k ?K ?g ?G ?s ?S ?C ?t ?T)))) (message "")) (t (org-clock-resolve-clock @@ -1092,7 +1142,7 @@ to be CLOCKED OUT.")))) (t (error "Unexpected, please report this as a bug"))) (and gotback last-valid) - (memq ch '(?K ?G ?S)) + (memq ch '(?K ?G ?S ?T)) (and start-over (not (memq ch '(?K ?G ?S ?C)))) fail-quietly))))) @@ -1315,7 +1365,6 @@ the default behavior." (t (insert-before-markers "\n") (backward-char 1) - (org-indent-line) (when (and (save-excursion (end-of-line 0) (org-in-item-p))) @@ -1340,7 +1389,8 @@ the default behavior." start-time (org-current-time org-clock-rounding-minutes t))) (setq ts (org-insert-time-stamp org-clock-start-time - 'with-hm 'inactive)))) + 'with-hm 'inactive)) + (org-indent-line))) (move-marker org-clock-marker (point) (buffer-base-buffer)) (move-marker org-clock-hd-marker (save-excursion (org-back-to-heading t) (point)) @@ -1375,6 +1425,26 @@ the default behavior." (message "Clock starts at %s - %s" ts org--msg-extra) (run-hooks 'org-clock-in-hook)))))) +(defun org-clock-auto-clockout () + "Clock out the currently clocked in task if Emacs is idle. +See `org-clock-auto-clockout-timer' to set the idle time span. + +This is only effective when `org-clock-auto-clockout-insinuate' +is present in the user configuration." + (when (and (numberp org-clock-auto-clockout-timer) + org-clock-current-task) + (run-with-idle-timer + org-clock-auto-clockout-timer nil #'org-clock-out))) + +;;;###autoload +(defun org-clock-toggle-auto-clockout () + (interactive) + (if (memq 'org-clock-auto-clockout org-clock-in-hook) + (progn (remove-hook 'org-clock-in-hook #'org-clock-auto-clockout) + (message "Auto clock-out after idle time turned off")) + (add-hook 'org-clock-in-hook #'org-clock-auto-clockout t) + (message "Auto clock-out after idle time turned on"))) + ;;;###autoload (defun org-clock-in-last (&optional arg) "Clock in the last closed clocked item. @@ -1512,7 +1582,7 @@ line and position cursor in that line." (insert ":" drawer ":\n:END:\n") (org-indent-region beg (point)) (org-flag-region - (line-end-position -1) (1- (point)) t 'org-hide-drawer) + (line-end-position -1) (1- (point)) t 'outline) (forward-line -1)))) ;; When a clock drawer needs to be created because of the ;; number of clock items or simply if it is missing, collect @@ -1537,7 +1607,7 @@ line and position cursor in that line." (let ((end (point-marker))) (goto-char beg) (save-excursion (insert ":" drawer ":\n")) - (org-flag-region (line-end-position) (1- end) t 'org-hide-drawer) + (org-flag-region (line-end-position) (1- end) t 'outline) (org-indent-region (point) end) (forward-line) (unless org-log-states-order-reversed @@ -1579,7 +1649,7 @@ to, overriding the existing value of `org-clock-out-switch-to-state'." org-clock-out-switch-to-state)) (now (org-current-time org-clock-rounding-minutes)) ts te s h m remove) - (setq org-clock-out-time now) + (setq org-clock-out-time (or at-time now)) (save-excursion ; Do not replace this with `with-current-buffer'. (with-no-warnings (set-buffer (org-clocking-buffer))) (save-restriction @@ -1724,7 +1794,7 @@ Optional argument N tells to change by that many units." (delq 'org-mode-line-string global-mode-string)) (org-clock-restore-frame-title-format) (force-mode-line-update) - (error "No active clock")) + (user-error "No active clock")) (save-excursion ; Do not replace this with `with-current-buffer'. (with-no-warnings (set-buffer (org-clocking-buffer))) (goto-char org-clock-marker) @@ -1753,14 +1823,14 @@ With prefix arg SELECT, offer recently clocked tasks for selection." (m (cond (select (or (org-clock-select-task "Select task to go to: ") - (error "No task selected"))) + (user-error "No task selected"))) ((org-clocking-p) org-clock-marker) ((and org-clock-goto-may-find-recent-task (car org-clock-history) (marker-buffer (car org-clock-history))) (setq recent t) (car org-clock-history)) - (t (error "No active or recent clock task"))))) + (t (user-error "No active or recent clock task"))))) (pop-to-buffer-same-window (marker-buffer m)) (if (or (< m (point-min)) (> m (point-max))) (widen)) (goto-char m) @@ -1890,7 +1960,12 @@ PROPNAME lets you set a custom text property instead of :org-clock-minutes." "Return time, clocked on current item in total." (save-excursion (save-restriction - (org-narrow-to-subtree) + (if (and (featurep 'org-inlinetask) + (or (org-inlinetask-at-task-p) + (org-inlinetask-in-task-p))) + (narrow-to-region (save-excursion (org-inlinetask-goto-beginning) (point)) + (save-excursion (org-inlinetask-goto-end) (point))) + (org-narrow-to-subtree)) (org-clock-sum tstart) org-clock-file-total-minutes))) @@ -2067,7 +2142,10 @@ in the buffer and update it." (start (goto-char start))) (org-update-dblock)) -(org-dynamic-block-define "clocktable" #'org-clock-report) +;;;###autoload +(eval-after-load 'org + '(progn + (org-dynamic-block-define "clocktable" #'org-clock-report))) (defun org-day-of-week (day month year) "Return the day of the week as an integer." @@ -2310,7 +2388,7 @@ the currently selected interval size." (save-excursion (goto-char (point-at-bol)) (if (not (looking-at "^[ \t]*#\\+BEGIN:[ \t]+clocktable\\>.*?:block[ \t]+\\(\\S-+\\)")) - (error "Line needs a :block definition before this command works") + (user-error "Line needs a :block definition before this command works") (let* ((b (match-beginning 1)) (e (match-end 1)) (s (match-string 1)) block shift ins y mw d date wp m) @@ -2369,7 +2447,7 @@ the currently selected interval size." (encode-time 0 0 0 1 (+ mw n) y)))) (y (setq ins (number-to-string (+ y n)))))) - (t (error "Cannot shift clocktable block"))) + (t (user-error "Cannot shift clocktable block"))) (when ins (goto-char b) (insert ins) @@ -2384,20 +2462,21 @@ the currently selected interval size." (setq params (org-combine-plists org-clocktable-defaults params)) (catch 'exit (let* ((scope (plist-get params :scope)) + (base-buffer (org-base-buffer (current-buffer))) (files (pcase scope (`agenda (org-agenda-files t)) (`agenda-with-archives (org-add-archive-files (org-agenda-files t))) (`file-with-archives - (and buffer-file-name - (org-add-archive-files (list buffer-file-name)))) + (let ((base-file (buffer-file-name base-buffer))) + (and base-file + (org-add-archive-files (list base-file))))) ((or `nil `file `subtree `tree (and (pred symbolp) (guard (string-match "\\`tree\\([0-9]+\\)\\'" (symbol-name scope))))) - (or (buffer-file-name (buffer-base-buffer)) - (current-buffer))) + base-buffer) ((pred functionp) (funcall scope)) ((pred consp) scope) (_ (user-error "Unknown scope: %S" scope)))) @@ -2421,7 +2500,7 @@ the currently selected interval size." (when step ;; Write many tables, in steps (unless (or block (and ts te)) - (error "Clocktable `:step' can only be used with `:block' or `:tstart,:end'")) + (user-error "Clocktable `:step' can only be used with `:block' or `:tstart, :end'")) (org-clocktable-steps params) (throw 'exit nil)) @@ -2527,7 +2606,7 @@ from the dynamic block definition." (guard (string-match-p "\\`[0-9]+!\\'" (symbol-name narrow)))) (setq narrow-cut-p t) (setq narrow (string-to-number (symbol-name narrow)))) - (_ (error "Invalid value %s of :narrow property in clock table" narrow))) + (_ (user-error "Invalid value %s of :narrow property in clock table" narrow))) ;; Now we need to output this table stuff. (goto-char ipos) @@ -2718,6 +2797,7 @@ a number of clock tables." (pcase step (`day "Daily report: ") (`week "Weekly report starting on: ") + (`semimonth "Semimonthly report starting on: ") (`month "Monthly report starting on: ") (`year "Annual report starting on: ") (_ (user-error "Unknown `:step' specification: %S" step)))) @@ -2767,6 +2847,9 @@ a number of clock tables." (let ((offset (if (= dow week-start) 7 (mod (- week-start dow) 7)))) (list 0 0 org-extend-today-until (+ d offset) m y))) + (`semimonth (list 0 0 0 + (if (< d 16) 16 1) + (if (< d 16) m (1+ m)) y)) (`month (list 0 0 0 month-start (1+ m) y)) (`year (list 0 0 org-extend-today-until 1 1 (1+ y))))))) (table-begin (line-beginning-position 0)) @@ -2883,7 +2966,7 @@ PROPERTIES: The list properties specified in the `:properties' parameter (org-trim (org-link-display-format (replace-regexp-in-string - "\\[[0-9]+%\\]\\|\\[[0-9]+/[0-9]+\\]" "" + "\\[[0-9]*\\(?:%\\|/[0-9]*\\)\\]" "" headline))))))) (tgs (and tags (org-get-tags))) (tsp diff --git a/lisp/org/org-colview.el b/lisp/org/org-colview.el index e967154abbc..565bdb2ddee 100644 --- a/lisp/org/org-colview.el +++ b/lisp/org/org-colview.el @@ -44,6 +44,8 @@ (declare-function org-dynamic-block-define "org" (type func)) (declare-function org-link-display-format "ol" (s)) (declare-function org-link-open-from-string "ol" (s &optional arg)) +(declare-function face-remap-remove-relative "face-remap" (cookie)) +(declare-function face-remap-add-relative "face-remap" (face &rest specs)) (defvar org-agenda-columns-add-appointments-to-effort-sum) (defvar org-agenda-columns-compute-summary-properties) @@ -164,7 +166,7 @@ See `org-columns-summary-types' for details.") (org-defkey org-columns-map "o" 'org-overview) (org-defkey org-columns-map "e" 'org-columns-edit-value) (org-defkey org-columns-map "\C-c\C-t" 'org-columns-todo) -(org-defkey org-columns-map "\C-c\C-c" 'org-columns-set-tags-or-toggle) +(org-defkey org-columns-map "\C-c\C-c" 'org-columns-toggle-or-columns-quit) (org-defkey org-columns-map "\C-c\C-o" 'org-columns-open-link) (org-defkey org-columns-map "v" 'org-columns-show-value) (org-defkey org-columns-map "q" 'org-columns-quit) @@ -257,6 +259,8 @@ value for ITEM property." (if org-hide-leading-stars ?\s ?*)) "* ")))) (concat stars (org-link-display-format value)))) + (`(,(or "DEADLINE" "SCHEDULED" "TIMESTAMP") . ,_) + (replace-regexp-in-string org-ts-regexp "[\\1]" value)) (`(,_ ,_ ,_ ,_ nil) value) ;; If PRINTF is set, assume we are displaying a number and ;; obey to the format string. @@ -364,11 +368,18 @@ ORIGINAL is the real string, i.e., before it is modified by ("TODO" (propertize v 'face (org-get-todo-face original))) (_ v))))) +(defvar org-columns-header-line-remap nil + "Store the relative remapping of column header-line. +This is needed to later remove this relative remapping.") + (defun org-columns--display-here (columns &optional dateline) "Overlay the current line with column display. COLUMNS is an alist (SPEC VALUE DISPLAYED). Optional argument DATELINE is non-nil when the face used should be `org-agenda-column-dateline'." + (when (ignore-errors (require 'face-remap)) + (setq org-columns-header-line-remap + (face-remap-add-relative 'header-line '(:inherit default)))) (save-excursion (beginning-of-line) (let* ((level-face (and (looking-at "\\(\\**\\)\\(\\* \\)") @@ -378,8 +389,7 @@ DATELINE is non-nil when the face used should be (org-get-at-bol 'face)) 'default)) (color (list :foreground (face-attribute ref-face :foreground))) - (font (list :height (face-attribute 'default :height) - :family (face-attribute 'default :family))) + (font (list :family (face-attribute 'default :family))) (face (list color font 'org-column ref-face)) (face1 (list color font 'org-agenda-column-dateline ref-face))) ;; Each column is an overlay on top of a character. So there has @@ -502,6 +512,9 @@ for the duration of the command.") (defun org-columns-remove-overlays () "Remove all currently active column overlays." (interactive) + (when (and (fboundp 'face-remap-remove-relative) + org-columns-header-line-remap) + (face-remap-remove-relative org-columns-header-line-remap)) (when org-columns-overlays (when (local-variable-p 'org-previous-header-line-format) (setq header-line-format org-previous-header-line-format) @@ -554,13 +567,19 @@ for the duration of the command.") (interactive "P") (org-columns-edit-value "TODO")) -(defun org-columns-set-tags-or-toggle (&optional _arg) - "Toggle checkbox at point, or set tags for current headline." - (interactive "P") - (if (string-match "\\`\\[[ xX-]\\]\\'" - (get-char-property (point) 'org-columns-value)) - (org-columns-next-allowed-value) - (org-columns-edit-value "TAGS"))) +(defun org-columns-toggle-or-columns-quit () + "Toggle checkbox at point, or quit column view." + (interactive) + (or (org-columns--toggle) + (org-columns-quit))) + +(defun org-columns--toggle () + "Toggle checkbox at point. Return non-nil if toggle happened, else nil. +See info documentation about realizing a suitable checkbox." + (when (string-match "\\`\\[[ xX-]\\]\\'" + (get-char-property (point) 'org-columns-value)) + (org-columns-next-allowed-value) + t)) (defvar org-overriding-columns-format nil "When set, overrides any other format definition for the agenda. @@ -1550,7 +1569,10 @@ PARAMS is a property list of parameters: (id))))) (org-update-dblock)) -(org-dynamic-block-define "columnview" #'org-columns-insert-dblock) +;;;###autoload +(eval-after-load 'org + '(progn + (org-dynamic-block-define "columnview" #'org-columns-insert-dblock))) ;;; Column view in the agenda @@ -1564,6 +1586,7 @@ PARAMS is a property list of parameters: (move-marker org-columns-begin-marker (point)) (setq org-columns-begin-marker (point-marker))) (let* ((org-columns--time (float-time)) + (org-done-keywords org-done-keywords-for-agenda) (fmt (cond ((bound-and-true-p org-overriding-columns-format)) @@ -1613,6 +1636,7 @@ PARAMS is a property list of parameters: (dolist (entry cache) (goto-char (car entry)) (org-columns--display-here (cdr entry))) + (setq-local org-agenda-columns-active t) (when org-agenda-columns-show-summaries (org-agenda-colview-summarize cache))))))) @@ -1677,8 +1701,7 @@ This will add overlays to the date lines, to show the summary for each day." 'face 'bold final)) (list spec final final))))) fmt) - 'dateline) - (setq-local org-agenda-columns-active t)))) + 'dateline)))) (if (bobp) (throw :complete t) (forward-line -1))))))) (defun org-agenda-colview-compute (fmt) @@ -1704,4 +1727,8 @@ This will add overlays to the date lines, to show the summary for each day." (provide 'org-colview) +;; Local variables: +;; generated-autoload-file: "org-loaddefs.el" +;; End: + ;;; org-colview.el ends here diff --git a/lisp/org/org-compat.el b/lisp/org/org-compat.el index c1aaf17ca2b..e4d8658197c 100644 --- a/lisp/org/org-compat.el +++ b/lisp/org/org-compat.el @@ -46,11 +46,13 @@ (declare-function org-end-of-subtree "org" (&optional invisible-ok to-heading)) (declare-function org-get-heading "org" (&optional no-tags no-todo no-priority no-comment)) (declare-function org-get-tags "org" (&optional pos local)) +(declare-function org-hide-block-toggle "org" (&optional force no-error element)) (declare-function org-link-display-format "ol" (s)) (declare-function org-link-set-parameters "ol" (type &rest rest)) (declare-function org-log-into-drawer "org" ()) (declare-function org-make-tag-string "org" (tags)) (declare-function org-reduced-level "org" (l)) +(declare-function org-return "org" (&optional indent arg interactive)) (declare-function org-show-context "org" (&optional key)) (declare-function org-table-end "org-table" (&optional table-type)) (declare-function outline-next-heading "outline" ()) @@ -101,6 +103,20 @@ is nil)." (defun org-time-convert-to-list (time) (seconds-to-time (float-time time)))) +;; `newline-and-indent' did not take a numeric argument before 27.1. +(if (version< emacs-version "27") + (defsubst org-newline-and-indent (&optional _arg) + (newline-and-indent)) + (defalias 'org-newline-and-indent #'newline-and-indent)) + +(defun org--set-faces-extend (faces extend-p) + "Set the :extend attribute of FACES to EXTEND-P. + +This is a no-op for Emacs versions lower than 27, since face +extension beyond end of line was not controllable." + (when (fboundp 'set-face-extend) + (mapc (lambda (f) (set-face-extend f extend-p)) faces))) + ;;; Emacs < 26.1 compatibility @@ -314,6 +330,8 @@ Counting starts at 1." (define-obsolete-variable-alias 'org-attach-directory 'org-attach-id-dir "Org 9.3") +(make-obsolete 'org-attach-store-link "No longer used" "Org 9.4") +(make-obsolete 'org-attach-expand-link "No longer used" "Org 9.4") (defun org-in-fixed-width-region-p () "Non-nil if point in a fixed-width region." @@ -556,6 +574,11 @@ use of this function is for the stuck project list." (define-obsolete-function-alias 'org-make-link-regexps 'org-link-make-regexps "Org 9.3") +(define-obsolete-function-alias 'org-property-global-value + 'org-property-global-or-keyword-value "Org 9.3") + +(make-obsolete-variable 'org-file-properties 'org-keyword-properties "Org 9.3") + (define-obsolete-variable-alias 'org-angle-link-re 'org-link-angle-re "Org 9.3") @@ -616,6 +639,72 @@ use of this function is for the stuck project list." (declare (obsolete "use `org-align-tags' instead." "Org 9.2")) (org-align-tags t)) +(define-obsolete-function-alias + 'org-at-property-block-p 'org-at-property-drawer-p "Org 9.4") + +(defun org-flag-drawer (flag &optional element beg end) + "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. + +When buffer positions BEG and END are provided, hide or show that +region as a drawer without further ado." + (declare (obsolete "use `org-hide-drawer-toggle' instead." "Org 9.4")) + (if (and beg end) (org-flag-region beg end flag 'outline) + (let ((drawer + (or element + (and (save-excursion + (beginning-of-line) + (looking-at-p "^[ \t]*:\\(\\(?:\\w\\|[-_]\\)+\\):[ \t]*$")) + (org-element-at-point))))) + (when (memq (org-element-type drawer) '(drawer property-drawer)) + (let ((post (org-element-property :post-affiliated drawer))) + (org-flag-region + (save-excursion (goto-char post) (line-end-position)) + (save-excursion (goto-char (org-element-property :end drawer)) + (skip-chars-backward " \t\n") + (line-end-position)) + flag 'outline) + ;; When the drawer is hidden away, make sure point lies in + ;; a visible part of the buffer. + (when (invisible-p (max (1- (point)) (point-min))) + (goto-char post))))))) + +(defun org-hide-block-toggle-maybe () + "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." + (declare (obsolete "use `org-hide-block-toggle' instead." "Org 9.4")) + (interactive) + (org-hide-block-toggle nil t)) + +(defun org-hide-block-toggle-all () + "Toggle the visibility of all blocks in the current buffer." + (declare (obsolete "please notify Org mailing list if you use this function." + "Org 9.4")) + (let ((start (point-min)) + (end (point-max))) + (save-excursion + (goto-char start) + (while (and (< (point) end) + (re-search-forward "^[ \t]*#\\+begin_?\ +\\([^ \n]+\\)\\(\\([^\n]+\\)\\)?\n\\([^\000]+?\\)#\\+end_?\\1[ \t]*$" end t)) + (save-excursion + (save-match-data + (goto-char (match-beginning 0)) + (org-hide-block-toggle))))))) + +(defun org-return-indent () + "Goto next table row or insert a newline and indent. +Calls `org-table-next-row' or `newline-and-indent', depending on +context. See the individual commands for more information." + (declare (obsolete "use `org-return' with INDENT set to t instead." + "Org 9.4")) + (interactive) + (org-return t)) + (defmacro org-with-silent-modifications (&rest body) (declare (obsolete "use `with-silent-modifications' instead." "Org 9.2") (debug (body))) @@ -624,6 +713,23 @@ use of this function is for the stuck project list." (define-obsolete-function-alias 'org-babel-strip-quotes 'org-strip-quotes "Org 9.2") +(define-obsolete-variable-alias 'org-sort-agenda-notime-is-late + 'org-agenda-sort-notime-is-late "9.4") + +(define-obsolete-variable-alias 'org-sort-agenda-noeffort-is-high + 'org-agenda-sort-noeffort-is-high "9.4") + +(defconst org-maybe-keyword-time-regexp + (concat "\\(\\<\\(\\(?:CLO\\(?:CK\\|SED\\)\\|DEADLINE\\|SCHEDULED\\):\\)\\)?" + " *\\([[<][0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} [^]\r\n>]*[]>]" + "\\|" + "<%%([^\r\n>]*>\\)") + "Matches a timestamp, possibly preceded by a keyword.") +(make-obsolete-variable + 'org-maybe-keyword-time-regexp + "use `org-planning-line-re', followed by `org-ts-regexp-both' instead." + "Org 9.4") + ;;;; Obsolete link types (eval-after-load 'ol @@ -808,7 +914,7 @@ This also applied for speedbar access." (setq last-level level))))) (aref subs 1)))) -(eval-after-load "imenu" +(eval-after-load 'imenu '(progn (add-hook 'imenu-after-jump-hook (lambda () @@ -870,7 +976,7 @@ To get rid of the restriction, use `\\[org-agenda-remove-restriction-lock]'." (defvar speedbar-file-key-map) (declare-function speedbar-add-supported-extension "speedbar" (extension)) -(eval-after-load "speedbar" +(eval-after-load 'speedbar '(progn (speedbar-add-supported-extension ".org") (define-key speedbar-file-key-map "<" 'org-speedbar-set-agenda-restriction) @@ -980,7 +1086,7 @@ ELEMENT is the element at point." (flyspell-delete-region-overlays beg end))) (defvar flyspell-delayed-commands) -(eval-after-load "flyspell" +(eval-after-load 'flyspell '(add-to-list 'flyspell-delayed-commands 'org-self-insert-command)) ;;;; Bookmark @@ -994,7 +1100,7 @@ ELEMENT is the element at point." (org-show-context 'bookmark-jump))) ;; Make `bookmark-jump' shows the jump location if it was hidden. -(eval-after-load "bookmark" +(eval-after-load 'bookmark '(if (boundp 'bookmark-after-jump-hook) ;; We can use the hook (add-hook 'bookmark-after-jump-hook 'org-bookmark-jump-unhide) @@ -1043,17 +1149,18 @@ key." ((guard (not (lookup-key calendar-mode-map "c"))) (local-set-key "c" #'org-calendar-goto-agenda)) (_ nil)) - (unless (eq org-agenda-diary-file 'diary-file) + (unless (and (boundp 'org-agenda-diary-file) + (eq org-agenda-diary-file 'diary-file)) (local-set-key org-calendar-insert-diary-entry-key #'org-agenda-diary-entry))) -(eval-after-load "calendar" +(eval-after-load 'calendar '(add-hook 'calendar-mode-hook #'org--setup-calendar-bindings)) ;;;; Saveplace ;; Make sure saveplace shows the location if it was hidden -(eval-after-load "saveplace" +(eval-after-load 'saveplace '(defadvice save-place-find-file-hook (after org-make-visible activate) "Make the position visible." (org-bookmark-jump-unhide))) @@ -1061,7 +1168,7 @@ key." ;;;; Ecb ;; Make sure ecb shows the location if it was hidden -(eval-after-load "ecb" +(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." (when (derived-mode-p 'org-mode) @@ -1075,17 +1182,17 @@ key." (org-invisible-p)) (org-show-context 'mark-goto))) -(eval-after-load "simple" +(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" +(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" +(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))) @@ -1094,9 +1201,13 @@ key." ;; Make "session.el" ignore our circular variable. (defvar session-globals-exclude) -(eval-after-load "session" +(eval-after-load 'session '(add-to-list 'session-globals-exclude 'org-mark-ring)) (provide 'org-compat) +;; Local variables: +;; generated-autoload-file: "org-loaddefs.el" +;; End: + ;;; org-compat.el ends here diff --git a/lisp/org/org-crypt.el b/lisp/org/org-crypt.el index 4b46a3145f4..187560c5538 100644 --- a/lisp/org/org-crypt.el +++ b/lisp/org/org-crypt.el @@ -1,14 +1,8 @@ ;;; org-crypt.el --- Public Key Encryption for Org Entries -*- lexical-binding: t; -*- +;; ;; Copyright (C) 2007-2020 Free Software Foundation, Inc. -;; Emacs Lisp Archive Entry -;; Filename: org-crypt.el -;; Keywords: org-mode ;; Author: John Wiegley -;; Maintainer: Peter Jones -;; Description: Adds public key encryption to Org buffers -;; URL: http://www.newartisans.com/software/emacs.html -;; Compatibility: Emacs22 ;; This file is part of GNU Emacs. ;; @@ -47,9 +41,7 @@ ;; ;; 3. To later decrypt an entry, use `org-decrypt-entries' or ;; `org-decrypt-entry'. It might be useful to bind this to a key, -;; like C-c C-/. I hope that in the future, C-c C-r can be might -;; overloaded to also decrypt an entry if it's encrypted, since -;; that fits nicely with the meaning of "reveal". +;; like C-c C-/. ;; ;; 4. To automatically encrypt all necessary entries when saving a ;; file, call `org-crypt-use-before-save-magic' after loading @@ -60,10 +52,11 @@ ;; - Carsten Dominik ;; - Vitaly Ostanin -(require 'org) - ;;; Code: +(require 'org-macs) +(require 'org-compat) + (declare-function epg-decrypt-string "epg" (context cipher)) (declare-function epg-list-keys "epg" (context &optional name mode)) (declare-function epg-make-context "epg" @@ -74,6 +67,17 @@ (context plain recipients &optional sign always-trust)) (defvar epg-context) +(declare-function org-back-over-empty-lines "org" ()) +(declare-function org-back-to-heading "org" (&optional invisible-ok)) +(declare-function org-before-first-heading-p "org" ()) +(declare-function org-end-of-meta-data "org" (&optional full)) +(declare-function org-end-of-subtree "org" (&optional invisible-ok to-heading)) +(declare-function org-entry-get "org" (pom property &optional inherit literal-nil)) +(declare-function org-flag-subtree "org" (flag)) +(declare-function org-make-tags-matcher "org" (match)) +(declare-function org-previous-visible-heading "org" (arg)) +(declare-function org-scan-tags "org" (action matcher todo-only &optional start-level)) +(declare-function org-set-property "org" (property value)) (defgroup org-crypt nil "Org Crypt." @@ -90,9 +94,18 @@ See the \"Match syntax\" section of the org manual for more details." (defcustom org-crypt-key "" "The default key to use when encrypting the contents of a heading. -This setting can also be overridden in the CRYPTKEY property." - :type 'string - :group 'org-crypt) +If this variable is nil, always use symmetric encryption, unconditionally. + +Otherwise, The string is matched against all keys in the key ring. +In particular, the empty string matches no key. If no key is found, +look for the `epa-file-encrypt-to' local variable. Ultimately fall back +to symmetric encryption. + +This setting can be overridden in the CRYPTKEY property." + :group 'org-crypt + :type '(choice + (string :tag "Public key(s) matching") + (const :tag "Symmetric encryption" nil))) (defcustom org-crypt-disable-auto-save 'ask "What org-decrypt should do if `auto-save-mode' is enabled. @@ -118,6 +131,36 @@ nil : Leave auto-save-mode enabled. (const :tag "Ask" ask) (const :tag "Encrypt" encrypt))) +(defun org-crypt--encrypted-text (beg end) + "Return encrypted text in between BEG and END." + ;; Ignore indentation. + (replace-regexp-in-string + "^[ \t]*" "" + (buffer-substring-no-properties beg end))) + +(defun org-at-encrypted-entry-p () + "Is the current entry encrypted? +When the entry is encrypted, return a pair (BEG . END) where BEG +and END are buffer positions delimiting the encrypted area." + (org-with-wide-buffer + (unless (org-before-first-heading-p) + (org-back-to-heading t) + (org-end-of-meta-data 'standard) + (let ((case-fold-search nil) + (banner-start (rx (seq bol + (zero-or-more (any "\t ")) + "-----BEGIN PGP MESSAGE-----" + eol)))) + (when (looking-at banner-start) + (let ((start (point)) + (banner-end (rx (seq bol + (or (group (zero-or-more (any "\t ")) + "-----END PGP MESSAGE-----" + eol) + (seq (one-or-more "*") " ")))))) + (when (and (re-search-forward banner-end nil t) (match-string 1)) + (cons start (line-beginning-position 2))))))))) + (defun org-crypt-check-auto-save () "Check whether auto-save-mode is enabled for the current buffer. @@ -149,93 +192,99 @@ See `org-crypt-disable-auto-save'." (t nil)))) (defun org-crypt-key-for-heading () - "Return the encryption key for the current heading." - (save-excursion - (org-back-to-heading t) - (or (org-entry-get nil "CRYPTKEY" 'selective) - org-crypt-key - (and (boundp 'epa-file-encrypt-to) epa-file-encrypt-to) - (message "No crypt key set, using symmetric encryption.")))) - -(defun org-encrypt-string (str crypt-key) - "Return STR encrypted with CRYPT-KEY." - ;; Text and key have to be identical, otherwise we re-crypt. - (if (and (string= crypt-key (get-text-property 0 'org-crypt-key str)) - (string= (sha1 str) (get-text-property 0 'org-crypt-checksum str))) - (get-text-property 0 'org-crypt-text str) - (setq-local epg-context (epg-make-context nil t t)) - (epg-encrypt-string epg-context str (epg-list-keys epg-context crypt-key)))) - + "Return the encryption key(s) for the current heading. +Assume `epg-context' is set." + (and org-crypt-key + (or (epg-list-keys epg-context + (or (org-entry-get nil "CRYPTKEY" 'selective) + org-crypt-key)) + (bound-and-true-p epa-file-encrypt-to) + (progn + (message "No crypt key set, using symmetric encryption.") + nil)))) + +;;;###autoload (defun org-encrypt-entry () "Encrypt the content of the current headline." (interactive) - (require 'epg) - (org-with-wide-buffer - (org-back-to-heading t) - (setq-local epg-context (epg-make-context nil t t)) - (let ((start-heading (point))) - (org-end-of-meta-data) - (unless (looking-at-p "-----BEGIN PGP MESSAGE-----") - (let ((folded (org-invisible-p)) - (crypt-key (org-crypt-key-for-heading)) - (beg (point))) + (unless (org-at-encrypted-entry-p) + (require 'epg) + (setq-local epg-context (epg-make-context nil t t)) + (org-with-wide-buffer + (org-back-to-heading t) + (let ((start-heading (point)) + (crypt-key (org-crypt-key-for-heading)) + (folded? (org-invisible-p (line-beginning-position)))) + (org-end-of-meta-data 'standard) + (let ((beg (point)) + (folded-heading + (and folded? + (save-excursion + (org-previous-visible-heading 1) + (point))))) (goto-char start-heading) (org-end-of-subtree t t) (org-back-over-empty-lines) - (let ((contents (delete-and-extract-region beg (point)))) + (let* ((contents (delete-and-extract-region beg (point))) + (key (get-text-property 0 'org-crypt-key contents)) + (checksum (get-text-property 0 'org-crypt-checksum contents))) (condition-case err - (insert (org-encrypt-string contents crypt-key)) + (insert + ;; Text and key have to be identical, otherwise we + ;; re-crypt. + (if (and (equal crypt-key key) + (string= checksum (sha1 contents))) + (get-text-property 0 'org-crypt-text contents) + (epg-encrypt-string epg-context contents crypt-key))) ;; If encryption failed, make sure to insert back entry ;; contents in the buffer. - (error (insert contents) (error (nth 1 err))))) - (when folded - (goto-char start-heading) + (error + (insert contents) + (error (error-message-string err))))) + (when folded-heading + (goto-char folded-heading) (org-flag-subtree t)) nil))))) +;;;###autoload (defun org-decrypt-entry () "Decrypt the content of the current headline." (interactive) - (require 'epg) - (unless (org-before-first-heading-p) - (org-with-wide-buffer - (org-back-to-heading t) - (let ((heading-point (point)) - (heading-was-invisible-p - (save-excursion - (outline-end-of-heading) - (org-invisible-p)))) - (org-end-of-meta-data) - (when (looking-at "-----BEGIN PGP MESSAGE-----") - (org-crypt-check-auto-save) - (setq-local epg-context (epg-make-context nil t t)) - (let* ((end (save-excursion - (search-forward "-----END PGP MESSAGE-----") - (forward-line) - (point))) - (encrypted-text (buffer-substring-no-properties (point) end)) - (decrypted-text - (decode-coding-string - (epg-decrypt-string - epg-context - encrypted-text) - 'utf-8))) - ;; Delete region starting just before point, because the - ;; outline property starts at the \n of the heading. - (delete-region (1- (point)) end) - ;; Store a checksum of the decrypted and the encrypted - ;; text value. This allows reusing the same encrypted text - ;; if the text does not change, and therefore avoid a - ;; re-encryption process. - (insert "\n" (propertize decrypted-text - 'org-crypt-checksum (sha1 decrypted-text) - 'org-crypt-key (org-crypt-key-for-heading) - 'org-crypt-text encrypted-text)) - (when heading-was-invisible-p - (goto-char heading-point) - (org-flag-subtree t)) - nil)))))) + (pcase (org-at-encrypted-entry-p) + (`(,beg . ,end) + (require 'epg) + (setq-local epg-context (epg-make-context nil t t)) + (org-with-point-at beg + (org-crypt-check-auto-save) + (let* ((folded-heading + (and (org-invisible-p) + (save-excursion + (org-previous-visible-heading 1) + (point)))) + (encrypted-text (org-crypt--encrypted-text beg end)) + (decrypted-text + (decode-coding-string + (epg-decrypt-string epg-context encrypted-text) + 'utf-8))) + ;; Delete region starting just before point, because the + ;; outline property starts at the \n of the heading. + (delete-region (1- (point)) end) + ;; Store a checksum of the decrypted and the encrypted text + ;; value. This allows reusing the same encrypted text if the + ;; text does not change, and therefore avoid a re-encryption + ;; process. + (insert "\n" + (propertize decrypted-text + 'org-crypt-checksum (sha1 decrypted-text) + 'org-crypt-key (org-crypt-key-for-heading) + 'org-crypt-text encrypted-text)) + (when folded-heading + (goto-char folded-heading) + (org-flag-subtree t)) + nil))) + (_ nil))) +;;;###autoload (defun org-encrypt-entries () "Encrypt all top-level entries in the current buffer." (interactive) @@ -245,6 +294,7 @@ See `org-crypt-disable-auto-save'." (cdr (org-make-tags-matcher org-crypt-tag-matcher)) org--matcher-tags-todo-only))) +;;;###autoload (defun org-decrypt-entries () "Decrypt all entries in the current buffer." (interactive) @@ -254,14 +304,7 @@ See `org-crypt-disable-auto-save'." (cdr (org-make-tags-matcher org-crypt-tag-matcher)) org--matcher-tags-todo-only))) -(defun org-at-encrypted-entry-p () - "Is the current entry encrypted?" - (unless (org-before-first-heading-p) - (save-excursion - (org-back-to-heading t) - (search-forward "-----BEGIN PGP MESSAGE-----" - (save-excursion (outline-next-heading)) t)))) - +;;;###autoload (defun org-crypt-use-before-save-magic () "Add a hook to automatically encrypt entries before a file is saved to disk." (add-hook diff --git a/lisp/org/org-datetree.el b/lisp/org/org-datetree.el index 6469abef794..d4ccc84bb4b 100644 --- a/lisp/org/org-datetree.el +++ b/lisp/org/org-datetree.el @@ -51,11 +51,29 @@ Added time stamp is active unless value is `inactive'." ;;;###autoload (defun org-datetree-find-date-create (d &optional keep-restriction) - "Find or create an entry for date D. + "Find or create a day entry for date D. If KEEP-RESTRICTION is non-nil, do not widen the buffer. When it is nil, the buffer will be widened to make sure an existing date tree can be found. If it is the symbol `subtree-at-point', then the tree will be built under the headline at point." + (org-datetree--find-create-group d 'day keep-restriction)) + +;;;###autoload +(defun org-datetree-find-month-create (d &optional keep-restriction) + "Find or create a month entry for date D. +Compared to `org-datetree-find-date-create' this function creates +entries grouped by month instead of days. +If KEEP-RESTRICTION is non-nil, do not widen the buffer. +When it is nil, the buffer will be widened to make sure an existing date +tree can be found. If it is the symbol `subtree-at-point', then the tree +will be built under the headline at point." + (org-datetree--find-create-group d 'month keep-restriction)) + +(defun org-datetree--find-create-group + (d time-grouping &optional keep-restriction) + "Find or create an entry for date D. +If time-period is day, group entries by day. If time-period is +month, then group entries by month." (setq-local org-datetree-base-level 1) (save-restriction (if (eq keep-restriction 'subtree-at-point) @@ -84,9 +102,10 @@ will be built under the headline at point." (org-datetree--find-create "^\\*+[ \t]+%d-\\([01][0-9]\\) \\w+$" year month) - (org-datetree--find-create - "^\\*+[ \t]+%d-%02d-\\([0123][0-9]\\) \\w+$" - year month day)))) + (when (eq time-grouping 'day) + (org-datetree--find-create + "^\\*+[ \t]+%d-%02d-\\([0123][0-9]\\) \\w+$" + year month day))))) ;;;###autoload (defun org-datetree-find-iso-week-create (d &optional keep-restriction) @@ -166,6 +185,8 @@ inserted into the buffer." (defun org-datetree-insert-line (year &optional month day text) (delete-region (save-excursion (skip-chars-backward " \t\n") (point)) (point)) + (when (assq 'heading org-blank-before-new-entry) + (insert "\n")) (insert "\n" (make-string org-datetree-base-level ?*) " \n") (backward-char) (when month (org-do-demote)) diff --git a/lisp/org/org-duration.el b/lisp/org/org-duration.el index 155bfae6ca0..81d5a66d9c8 100644 --- a/lisp/org/org-duration.el +++ b/lisp/org/org-duration.el @@ -28,14 +28,16 @@ ;; - 3:12 ;; - 1:23:45 ;; - 1y 3d 3h 4min +;; - 1d3h5min ;; - 3d 13:35 ;; - 2.35h ;; ;; More accurately, it consists of numbers and units, as defined in -;; variable `org-duration-units', separated with white spaces, and -;; a "H:MM" or "H:MM:SS" part. White spaces are tolerated between the -;; number and its relative unit. Variable `org-duration-format' -;; controls durations default representation. +;; variable `org-duration-units', possibly separated with white +;; spaces, and an optional "H:MM" or "H:MM:SS" part, which always +;; comes last. White spaces are tolerated between the number and its +;; relative unit. Variable `org-duration-format' controls durations +;; default representation. ;; ;; The library provides functions allowing to convert a duration to, ;; and from, a number of minutes: `org-duration-to-minutes' and @@ -122,8 +124,7 @@ are specified here. Units with a zero value are skipped, unless REQUIRED? is non-nil. In that case, the unit is always used. -Eventually, the list can contain one of the following special -entries: +The list can also contain one of the following special entries: (special . h:mm) (special . h:mm:ss) @@ -139,6 +140,10 @@ entries: first one required or with a non-zero integer part. If there is no such unit, the smallest one is used. +Eventually, if the list contains the symbol `compact', the +duration is expressed in a compact form, without any white space +between units. + For example, ((\"d\" . nil) (\"h\" . t) (\"min\" . t)) @@ -172,7 +177,6 @@ a 2-digits fractional part, of \"d\" unit. A duration shorter than a day uses \"h\" unit instead." :group 'org-time :group 'org-clock - :version "26.1" :package-version '(Org . "9.1") :type '(choice (const :tag "Use H:MM" h:mm) @@ -191,7 +195,8 @@ than a day uses \"h\" unit instead." (const h:mm)) (cons :tag "Use both units and H:MM:SS" (const special) - (const h:mm:ss)))))) + (const h:mm:ss)) + (const :tag "Use compact form" compact))))) ;;; Internal variables and functions @@ -249,13 +254,10 @@ When optional argument CANONICAL is non-nil, refer to org-duration-units)) t))) (setq org-duration--full-re - (format "\\`[ \t]*%s\\(?:[ \t]+%s\\)*[ \t]*\\'" - org-duration--unit-re - org-duration--unit-re)) + (format "\\`\\(?:[ \t]*%s\\)+[ \t]*\\'" org-duration--unit-re)) (setq org-duration--mixed-re - (format "\\`[ \t]*\\(?1:%s\\(?:[ \t]+%s\\)*\\)[ \t]+\ + (format "\\`\\(?1:\\([ \t]*%s\\)+\\)[ \t]*\ \\(?2:[0-9]+\\(?::[0-9][0-9]\\)\\{1,2\\}\\)[ \t]*\\'" - org-duration--unit-re org-duration--unit-re))) ;;;###autoload @@ -353,10 +355,11 @@ Raise an error if expected format is unknown." ;; Represent minutes above hour using provided units and H:MM ;; or H:MM:SS below. (let* ((units-part (* min-modifier (/ (floor minutes) min-modifier))) - (minutes-part (- minutes units-part))) + (minutes-part (- minutes units-part)) + (compact (memq 'compact duration-format))) (concat (org-duration-from-minutes units-part truncated-format canonical) - " " + (and (not compact) " ") (org-duration-from-minutes minutes-part mode)))))) ;; Units format. (duration-format @@ -368,12 +371,16 @@ Raise an error if expected format is unknown." (format "%%.%df" digits)))) (selected-units (sort (cl-remove-if - ;; Ignore special format cells. - (lambda (pair) (pcase pair (`(special . ,_) t) (_ nil))) + ;; Ignore special format cells and compact option. + (lambda (pair) + (pcase pair + ((or `compact `(special . ,_)) t) + (_ nil))) duration-format) (lambda (a b) (> (org-duration--modifier (car a) canonical) - (org-duration--modifier (car b) canonical)))))) + (org-duration--modifier (car b) canonical))))) + (separator (if (memq 'compact duration-format) "" " "))) (cond ;; Fractional duration: use first unit that is either required ;; or smaller than MINUTES. @@ -402,8 +409,8 @@ Raise an error if expected format is unknown." (cond ((<= modifier minutes) (let ((value (floor minutes modifier))) (cl-decf minutes (* value modifier)) - (format " %d%s" value unit))) - (required? (concat " 0" unit)) + (format "%s%d%s" separator value unit))) + (required? (concat separator "0" unit)) (t "")))) selected-units "")))) @@ -441,4 +448,9 @@ with \"H:MM:SS\" format, return `h:mm:ss'. Otherwise, return (org-duration-set-regexps) (provide 'org-duration) + +;; Local variables: +;; generated-autoload-file: "org-loaddefs.el" +;; End: + ;;; org-duration.el ends here diff --git a/lisp/org/org-element.el b/lisp/org/org-element.el index 4b5f9a19e6d..2ad557d2179 100644 --- a/lisp/org/org-element.el +++ b/lisp/org/org-element.el @@ -72,7 +72,6 @@ (declare-function org-at-heading-p "org" (&optional _)) (declare-function org-end-of-subtree "org" (&optional invisible-ok to-heading)) (declare-function org-escape-code-in-string "org-src" (s)) -(declare-function org-find-visible "org" ()) (declare-function org-macro-escape-arguments "org-macro" (&rest args)) (declare-function org-macro-extract-arguments "org-macro" (s)) (declare-function org-reduced-level "org" (l)) @@ -330,7 +329,9 @@ match group 2. Don't modify it, set `org-element-affiliated-keywords' instead.") (defconst org-element-object-restrictions - (let* ((standard-set (remq 'table-cell org-element-all-objects)) + (let* ((minimal-set '(bold code entity italic latex-fragment strike-through + subscript superscript underline verbatim)) + (standard-set (remq 'table-cell org-element-all-objects)) (standard-set-no-line-break (remq 'line-break standard-set))) `((bold ,@standard-set) (footnote-reference ,@standard-set) @@ -341,23 +342,20 @@ Don't modify it, set `org-element-affiliated-keywords' instead.") (keyword ,@(remq 'footnote-reference standard-set)) ;; Ignore all links in a link description. Also ignore ;; radio-targets and line breaks. - (link bold code entity export-snippet inline-babel-call inline-src-block - italic latex-fragment macro statistics-cookie strike-through - subscript superscript underline verbatim) + (link export-snippet inline-babel-call inline-src-block macro + statistics-cookie ,@minimal-set) (paragraph ,@standard-set) ;; Remove any variable object from radio target as it would ;; prevent it from being properly recognized. - (radio-target bold code entity italic latex-fragment strike-through - subscript superscript underline superscript) + (radio-target ,@minimal-set) (strike-through ,@standard-set) (subscript ,@standard-set) (superscript ,@standard-set) ;; Ignore inline babel call and inline source block as formulas ;; are possible. Also ignore line breaks and statistics ;; cookies. - (table-cell bold code entity export-snippet footnote-reference italic - latex-fragment link macro radio-target strike-through - subscript superscript target timestamp underline verbatim) + (table-cell export-snippet footnote-reference link macro radio-target + target timestamp ,@minimal-set) (table-row table-cell) (underline ,@standard-set) (verse-block ,@standard-set))) @@ -367,10 +365,6 @@ key is an element or object type containing objects and value is a list of types that can be contained within an element or object of such type. -For example, in a `radio-target' object, one can only find -entities, latex-fragments, subscript, superscript and text -markup. - This alist also applies to secondary string. For example, an `headline' type element doesn't directly contain objects, but still has an entry since one of its properties (`:title') does.") @@ -1806,13 +1800,10 @@ Return a list whose CAR is `clock' and CDR is a plist containing ;;;; Comment -(defun org-element-comment-parser (limit affiliated) +(defun org-element-comment-parser (limit) "Parse a comment. -LIMIT bounds the search. AFFILIATED is a list of which CAR is -the buffer position at the beginning of the first affiliated -keyword and CDR is a plist of affiliated keywords along with -their value. +LIMIT bounds the search. Return a list whose CAR is `comment' and CDR is a plist containing `:begin', `:end', `:value', `:post-blank', @@ -1820,8 +1811,7 @@ containing `:begin', `:end', `:value', `:post-blank', Assume point is at comment beginning." (save-excursion - (let* ((begin (car affiliated)) - (post-affiliated (point)) + (let* ((begin (point)) (value (prog2 (looking-at "[ \t]*# ?") (buffer-substring-no-properties (match-end 0) (line-end-position)) @@ -1843,13 +1833,11 @@ Assume point is at comment beginning." (skip-chars-forward " \r\t\n" limit) (if (eobp) (point) (line-beginning-position))))) (list 'comment - (nconc - (list :begin begin - :end end - :value value - :post-blank (count-lines com-end end) - :post-affiliated post-affiliated) - (cdr affiliated)))))) + (list :begin begin + :end end + :value value + :post-blank (count-lines com-end end) + :post-affiliated begin))))) (defun org-element-comment-interpreter (comment _) "Interpret COMMENT element as Org syntax. @@ -2186,9 +2174,9 @@ the buffer position at the beginning of the first affiliated keyword and CDR is a plist of affiliated keywords along with their value. -Return a list whose CAR is `keyword' and CDR is a plist -containing `:key', `:value', `:begin', `:end', `:post-blank' and -`:post-affiliated' keywords." +Return a list whose CAR is a normalized `keyword' (uppercase) and +CDR is a plist containing `:key', `:value', `:begin', `:end', +`:post-blank' and `:post-affiliated' keywords." (save-excursion ;; An orphaned affiliated keyword is considered as a regular ;; keyword. In this case AFFILIATED is nil, so we take care of @@ -3217,10 +3205,11 @@ Assume point is at the beginning of the link." (setq post-blank (progn (goto-char link-end) (skip-chars-forward " \t"))) (setq end (point))) - ;; Special "file" type link processing. Extract opening + ;; Special "file"-type link processing. Extract opening ;; application and search option, if any. Also normalize URI. (when (string-match "\\`file\\(?:\\+\\(.+\\)\\)?\\'" type) - (setq application (match-string 1 type) type "file") + (setq application (match-string 1 type)) + (setq type "file") (when (string-match "::\\(.*\\)\\'" path) (setq search-option (match-string 1 path)) (setq path (replace-match "" nil nil path))) @@ -3823,12 +3812,6 @@ Assume point is at the first equal sign marker." ;; `org-element--current-element' is the core function of this section. ;; It returns the Lisp representation of the element starting at ;; point. -;; -;; `org-element--current-element' makes use of special modes. They -;; are activated for fixed element chaining (e.g., `plain-list' > -;; `item') or fixed conditional element chaining (e.g., `headline' > -;; `section'). Special modes are: `first-section', `item', -;; `node-property', `section' and `table-row'. (defun org-element--current-element (limit &optional granularity mode structure) "Parse the element starting at point. @@ -3848,8 +3831,9 @@ nil), secondary values will not be parsed, since they only contain objects. Optional argument MODE, when non-nil, can be either -`first-section', `section', `planning', `item', `node-property' -and `table-row'. +`first-section', `item', `node-property', `planning', +`property-drawer', `section', `table-row', or `top-comment'. + If STRUCTURE isn't provided but MODE is set to `item', it will be computed. @@ -3879,15 +3863,22 @@ element it has to parse." (org-element-section-parser (or (save-excursion (org-with-limited-levels (outline-next-heading))) limit))) + ;; Comments. + ((looking-at "^[ \t]*#\\(?: \\|$\\)") + (org-element-comment-parser limit)) ;; Planning. ((and (eq mode 'planning) (eq ?* (char-after (line-beginning-position 0))) (looking-at org-planning-line-re)) (org-element-planning-parser limit)) ;; Property drawer. - ((and (memq mode '(planning property-drawer)) - (eq ?* (char-after (line-beginning-position - (if (eq mode 'planning) 0 -1)))) + ((and (pcase mode + (`planning (eq ?* (char-after (line-beginning-position 0)))) + ((or `property-drawer `top-comment) + (save-excursion + (beginning-of-line 0) + (not (looking-at "[[:blank:]]*$")))) + (_ nil)) (looking-at org-property-drawer-re)) (org-element-property-drawer-parser limit)) ;; When not at bol, point is at the beginning of an item or @@ -3896,7 +3887,7 @@ element it has to parse." ;; Clock. ((looking-at org-clock-line-re) (org-element-clock-parser limit)) ;; Inlinetask. - ((org-at-heading-p) + ((looking-at "^\\*+ ") (org-element-inlinetask-parser limit raw-secondary-p)) ;; From there, elements can have affiliated keywords. (t (let ((affiliated (org-element--collect-affiliated-keywords @@ -3910,7 +3901,7 @@ element it has to parse." ;; LaTeX Environment. ((looking-at org-element--latex-begin-environment) (org-element-latex-environment-parser limit affiliated)) - ;; Drawer and Property Drawer. + ;; Drawer. ((looking-at org-drawer-regexp) (org-element-drawer-parser limit affiliated)) ;; Fixed Width @@ -3918,13 +3909,10 @@ element it has to parse." (org-element-fixed-width-parser limit affiliated)) ;; Inline Comments, Blocks, Babel Calls, Dynamic Blocks and ;; Keywords. - ((looking-at "[ \t]*#") + ((looking-at "[ \t]*#\\+") (goto-char (match-end 0)) (cond - ((looking-at "\\(?: \\|$\\)") - (beginning-of-line) - (org-element-comment-parser limit affiliated)) - ((looking-at "\\+BEGIN_\\(\\S-+\\)") + ((looking-at "BEGIN_\\(\\S-+\\)") (beginning-of-line) (funcall (pcase (upcase (match-string 1)) ("CENTER" #'org-element-center-block-parser) @@ -3937,13 +3925,13 @@ element it has to parse." (_ #'org-element-special-block-parser)) limit affiliated)) - ((looking-at "\\+CALL:") + ((looking-at "CALL:") (beginning-of-line) (org-element-babel-call-parser limit affiliated)) - ((looking-at "\\+BEGIN:? ") + ((looking-at "BEGIN:? ") (beginning-of-line) (org-element-dynamic-block-parser limit affiliated)) - ((looking-at "\\+\\S-+:") + ((looking-at "\\S-+:") (beginning-of-line) (org-element-keyword-parser limit affiliated)) (t @@ -4024,7 +4012,8 @@ When PARSE is non-nil, values from keywords belonging to (skip-chars-backward " \t") (point)))) (if parsed? - (org-element--parse-objects beg end nil restrict) + (save-match-data + (org-element--parse-objects beg end nil restrict)) (org-trim (buffer-substring-no-properties beg end))))) ;; If KWD is a dual keyword, find its secondary value. ;; Maybe parse it. @@ -4144,7 +4133,9 @@ If STRING is the empty string or nil, return nil." (dolist (v local-variables) (ignore-errors (if (symbolp v) (makunbound v) - (set (make-local-variable (car v)) (cdr v))))) + ;; Don't set file name to avoid mishandling hooks (bug#44524) + (unless (memq (car v) '(buffer-file-name buffer-file-truename)) + (set (make-local-variable (car v)) (cdr v)))))) ;; Transferring local variables may put the temporary buffer ;; into a read-only state. Make sure we can insert STRING. (let ((inhibit-read-only t)) (insert string)) @@ -4320,34 +4311,41 @@ looking into captions: ;; `org-element--object-lex' to find the next object in the current ;; container. -(defsubst org-element--next-mode (type parentp) - "Return next special mode according to TYPE, or nil. -TYPE is a symbol representing the type of an element or object -containing next element if PARENTP is non-nil, or before it -otherwise. Modes can be either `first-section', `item', -`node-property', `planning', `property-drawer', `section', -`table-row' or nil." - (if parentp +(defsubst org-element--next-mode (mode type parent?) + "Return next mode according to current one. + +MODE is a symbol representing the expectation about the next +element or object. Meaningful values are `first-section', +`item', `node-property', `planning', `property-drawer', +`section', `table-row', `top-comment', and nil. + +TYPE is the type of the current element or object. + +If PARENT? is non-nil, assume the next element or object will be +located inside the current one. " + (if parent? (pcase type (`headline 'section) + ((and (guard (eq mode 'first-section)) `section) 'top-comment) (`inlinetask 'planning) (`plain-list 'item) (`property-drawer 'node-property) (`section 'planning) (`table 'table-row)) - (pcase type + (pcase mode (`item 'item) (`node-property 'node-property) - (`planning 'property-drawer) - (`table-row 'table-row)))) + ((and `planning (guard (eq type 'planning))) 'property-drawer) + (`table-row 'table-row) + ((and `top-comment (guard (eq type 'comment))) 'property-drawer)))) (defun org-element--parse-elements (beg end mode structure granularity visible-only acc) "Parse elements between BEG and END positions. MODE prioritizes some elements over the others. It can be set to -`first-section', `section', `planning', `item', `node-property' -or `table-row'. +`first-section', `item', `node-property', `planning', +`property-drawer', `section', `table-row', `top-comment', or nil. When value is `item', STRUCTURE will be used as the current list structure. @@ -4361,54 +4359,52 @@ elements. Elements are accumulated into ACC." (save-excursion (goto-char beg) - ;; Visible only: skip invisible parts at the beginning of the - ;; element. - (when (and visible-only (org-invisible-p2)) - (goto-char (min (1+ (org-find-visible)) end))) ;; When parsing only headlines, skip any text before first one. (when (and (eq granularity 'headline) (not (org-at-heading-p))) (org-with-limited-levels (outline-next-heading))) (let (elements) (while (< (point) end) - ;; Find current element's type and parse it accordingly to - ;; its category. - (let* ((element (org-element--current-element - end granularity mode structure)) - (type (org-element-type element)) - (cbeg (org-element-property :contents-begin element))) - (goto-char (org-element-property :end element)) - ;; Visible only: skip invisible parts between siblings. - (when (and visible-only (org-invisible-p2)) - (goto-char (min (1+ (org-find-visible)) end))) - ;; Fill ELEMENT contents by side-effect. - (cond - ;; If element has no contents, don't modify it. - ((not cbeg)) - ;; Greater element: parse it between `contents-begin' and - ;; `contents-end'. Make sure GRANULARITY allows the - ;; recursion, or ELEMENT is a headline, in which case going - ;; inside is mandatory, in order to get sub-level headings. - ((and (memq type org-element-greater-elements) - (or (memq granularity '(element object nil)) - (and (eq granularity 'greater-element) - (eq type 'section)) - (eq type 'headline))) - (org-element--parse-elements - cbeg (org-element-property :contents-end element) - ;; Possibly switch to a special mode. - (org-element--next-mode type t) - (and (memq type '(item plain-list)) - (org-element-property :structure element)) - granularity visible-only element)) - ;; ELEMENT has contents. Parse objects inside, if - ;; GRANULARITY allows it. - ((memq granularity '(object nil)) - (org-element--parse-objects - cbeg (org-element-property :contents-end element) element - (org-element-restriction type)))) - (push (org-element-put-property element :parent acc) elements) - ;; Update mode. - (setq mode (org-element--next-mode type nil)))) + ;; Visible only: skip invisible parts due to folding. + (if (and visible-only (org-invisible-p nil t)) + (progn + (goto-char (org-find-visible)) + (when (and (eolp) (not (eobp))) (forward-char))) + ;; Find current element's type and parse it accordingly to + ;; its category. + (let* ((element (org-element--current-element + end granularity mode structure)) + (type (org-element-type element)) + (cbeg (org-element-property :contents-begin element))) + (goto-char (org-element-property :end element)) + ;; Fill ELEMENT contents by side-effect. + (cond + ;; If element has no contents, don't modify it. + ((not cbeg)) + ;; Greater element: parse it between `contents-begin' and + ;; `contents-end'. Ensure GRANULARITY allows recursion, + ;; or ELEMENT is a headline, in which case going inside + ;; is mandatory, in order to get sub-level headings. + ((and (memq type org-element-greater-elements) + (or (memq granularity '(element object nil)) + (and (eq granularity 'greater-element) + (eq type 'section)) + (eq type 'headline))) + (org-element--parse-elements + cbeg (org-element-property :contents-end element) + ;; Possibly switch to a special mode. + (org-element--next-mode mode type t) + (and (memq type '(item plain-list)) + (org-element-property :structure element)) + granularity visible-only element)) + ;; ELEMENT has contents. Parse objects inside, if + ;; GRANULARITY allows it. + ((memq granularity '(object nil)) + (org-element--parse-objects + cbeg (org-element-property :contents-end element) element + (org-element-restriction type)))) + (push (org-element-put-property element :parent acc) elements) + ;; Update mode. + (setq mode (org-element--next-mode mode type nil))))) ;; Return result. (apply #'org-element-set-contents acc (nreverse elements))))) @@ -4498,15 +4494,21 @@ to an appropriate container (e.g., a paragraph)." (and (memq 'latex-fragment restriction) (org-element-latex-fragment-parser))))) (?\[ - (if (eq (aref result 1) ?\[) - (and (memq 'link restriction) - (org-element-link-parser)) - (or (and (memq 'footnote-reference restriction) - (org-element-footnote-reference-parser)) - (and (memq 'timestamp restriction) - (org-element-timestamp-parser)) - (and (memq 'statistics-cookie restriction) - (org-element-statistics-cookie-parser))))) + (pcase (aref result 1) + ((and ?\[ + (guard (memq 'link restriction))) + (org-element-link-parser)) + ((and ?f + (guard (memq 'footnote-reference restriction))) + (org-element-footnote-reference-parser)) + ((and (or ?% ?/) + (guard (memq 'statistics-cookie restriction))) + (org-element-statistics-cookie-parser)) + (_ + (or (and (memq 'timestamp restriction) + (org-element-timestamp-parser)) + (and (memq 'statistics-cookie restriction) + (org-element-statistics-cookie-parser)))))) ;; This is probably a plain link. (_ (and (memq 'link restriction) (org-element-link-parser))))))) @@ -4821,10 +4823,12 @@ indentation removed from its contents." ;; ;; A single public function is provided: `org-element-cache-reset'. ;; -;; Cache is enabled by default, but can be disabled globally with +;; Cache is disabled by default for now because it sometimes triggers +;; freezes, but it can be enabled globally with ;; `org-element-use-cache'. `org-element-cache-sync-idle-time', -;; org-element-cache-sync-duration' and `org-element-cache-sync-break' -;; can be tweaked to control caching behavior. +;; `org-element-cache-sync-duration' and +;; `org-element-cache-sync-break' can be tweaked to control caching +;; behavior. ;; ;; Internally, parsed elements are stored in an AVL tree, ;; `org-element--cache'. This tree is updated lazily: whenever @@ -4892,7 +4896,7 @@ with `org-element--cache-compare'. This cache is used in A request is a vector with the following pattern: - \[NEXT BEG END OFFSET PARENT PHASE] + [NEXT BEG END OFFSET PARENT PHASE] Processing a synchronization request consists of three phases: @@ -5450,9 +5454,11 @@ the process stopped before finding the expected result." ;; element following headline above, or first element in ;; buffer. ((not cached) - (when (org-with-limited-levels (outline-previous-heading)) - (setq mode 'planning) - (forward-line)) + (if (org-with-limited-levels (outline-previous-heading)) + (progn + (setq mode 'planning) + (forward-line)) + (setq mode 'top-comment)) (skip-chars-forward " \r\t\n") (beginning-of-line)) ;; Cache returned exact match: return it. @@ -5521,7 +5527,7 @@ the process stopped before finding the expected result." ;; after it. ((and (<= elem-end pos) (/= (point-max) elem-end)) (goto-char elem-end) - (setq mode (org-element--next-mode type nil))) + (setq mode (org-element--next-mode mode type nil))) ;; A non-greater element contains point: return it. ((not (memq type org-element-greater-elements)) (throw 'exit element)) @@ -5549,7 +5555,7 @@ the process stopped before finding the expected result." (and (= cend pos) (= (point-max) pos))))) (goto-char (or next cbeg)) (setq next nil - mode (org-element--next-mode type t) + mode (org-element--next-mode mode type t) parent element end cend)))) ;; Otherwise, return ELEMENT as it is the smallest @@ -5813,7 +5819,7 @@ element. Possible types are defined in `org-element-all-elements'. Properties depend on element or object type, but always include -`:begin', `:end', `:parent' and `:post-blank' properties. +`:begin', `:end', and `:post-blank' properties. As a special case, if point is at the very beginning of the first item in a list or sub-list, returned element will be that list diff --git a/lisp/org/org-entities.el b/lisp/org/org-entities.el index e32ce269b4a..bca0c4338a3 100644 --- a/lisp/org/org-entities.el +++ b/lisp/org/org-entities.el @@ -226,7 +226,7 @@ packages to be loaded, add these packages to `org-latex-packages-alist'." ("beth" "\\beth" t "ℶ" "beth" "beth" "ב") ("dalet" "\\daleth" t "ℸ" "dalet" "dalet" "ד") - "** Dead languages" + "** Icelandic" ("ETH" "\\DH{}" nil "Ð" "D" "Ð" "Ð") ("eth" "\\dh{}" nil "ð" "dh" "ð" "ð") ("THORN" "\\TH{}" nil "Þ" "TH" "Þ" "Þ") @@ -386,7 +386,7 @@ packages to be loaded, add these packages to `org-latex-packages-alist'." ("exists" "\\exists" t "∃" "[there exists]" "[there exists]" "∃") ("nexist" "\\nexists" t "∃" "[there does not exists]" "[there does not exists]" "∄") ("nexists" "\\nexists" t "∃" "[there does not exists]" "[there does not exists]" "∄") - ("empty" "\\empty" t "∅" "[empty set]" "[empty set]" "∅") + ("empty" "\\emptyset" t "∅" "[empty set]" "[empty set]" "∅") ("emptyset" "\\emptyset" t "∅" "[empty set]" "[empty set]" "∅") ("isin" "\\in" t "∈" "[element of]" "[element of]" "∈") ("in" "\\in" t "∈" "[element of]" "[element of]" "∈") diff --git a/lisp/org/org-faces.el b/lisp/org/org-faces.el index 30eab9bc6b7..c0556b8bbcd 100644 --- a/lisp/org/org-faces.el +++ b/lisp/org/org-faces.el @@ -243,6 +243,15 @@ is of course immediately visible, but for example a passed deadline is of the frame, for example." :group 'org-faces) +(defface org-headline-todo ;Copied from `font-lock-string-face' + '((((class color) (min-colors 16) (background light)) (:foreground "Red4")) + (((class color) (min-colors 16) (background dark)) (:foreground "Pink2")) + (((class color) (min-colors 8) (background light)) (:bold t))) + "Face used to indicate that a headline is marked as TODO. +This face is only used if `org-fontify-todo-headline' is set. If applies +to the part of the headline after the TODO keyword." + :group 'org-faces) + (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")) @@ -355,6 +364,12 @@ changes." "Face used for tables." :group 'org-faces) +(defface org-table-header '((t :inherit org-table + :background "LightGray" + :foreground "Black")) + "Face for table header." + :group 'org-faces) + (defface org-formula '((((class color) (min-colors 88) (background light)) (:foreground "Firebrick")) (((class color) (min-colors 88) (background dark)) (:foreground "chocolate1")) @@ -393,9 +408,17 @@ follows a #+DATE:, #+AUTHOR: or #+EMAIL: keyword." "Face for #+TITLE:, #+AUTHOR:, #+EMAIL: and #+DATE: keywords." :group 'org-faces) -(defface org-block '((t :inherit shadow)) - "Face text in #+begin ... #+end blocks. -For source-blocks `org-src-block-faces' takes precedence." +(defface org-block `((t :inherit shadow + ,@(and (>= emacs-major-version 27) '(:extend t)))) + "Face used for text inside various blocks. + +It is always used for source blocks. You can refine what face +should be used depending on the source block language by setting, +`org-src-block-faces', which takes precedence. + +When `org-fontify-quote-and-verse-blocks' is not nil, text inside +verse and quote blocks are fontified using the `org-verse' and +`org-quote' faces, which inherit from `org-block'." :group 'org-faces :version "26.1") diff --git a/lisp/org/org-goto.el b/lisp/org/org-goto.el index dd9c0fad577..93e6f940c75 100644 --- a/lisp/org/org-goto.el +++ b/lisp/org/org-goto.el @@ -22,27 +22,8 @@ ;;; Code: -(require 'org-macs) -(require 'org-compat) - -(declare-function org-at-heading-p "org" (&optional ignored)) -(declare-function org-beginning-of-line "org" (&optional n)) -(declare-function org-defkey "org" (keymap key def)) -(declare-function org-mark-ring-push "org" (&optional pos buffer)) -(declare-function org-overview "org" ()) -(declare-function org-refile-check-position "org" (refile-pointer)) -(declare-function org-refile-get-location "org" (&optional prompt default-buffer new-nodes)) -(declare-function org-show-context "org" (&optional key)) -(declare-function org-show-set-visibility "org" (detail)) - -(defvar org-complex-heading-regexp) -(defvar org-startup-align-all-tables) -(defvar org-startup-folded) -(defvar org-startup-truncated) -(defvar org-special-ctrl-a/e) -(defvar org-refile-target-verify-function) -(defvar org-refile-use-outline-path) -(defvar org-refile-targets) +(require 'org) +(require 'org-refile) (defvar org-goto-exit-command nil) (defvar org-goto-map nil) @@ -234,20 +215,15 @@ position or nil." (and (get-buffer "*org-goto*") (kill-buffer "*org-goto*")) (pop-to-buffer-same-window (condition-case nil - (make-indirect-buffer (current-buffer) "*org-goto*") - (error (make-indirect-buffer (current-buffer) "*org-goto*")))) + (make-indirect-buffer (current-buffer) "*org-goto*" t) + (error (make-indirect-buffer (current-buffer) "*org-goto*" t)))) (let (temp-buffer-show-function temp-buffer-show-hook) (with-output-to-temp-buffer "*Org Help*" (princ (format help (if org-goto-auto-isearch " Just type for auto-isearch." " n/p/f/b/u to navigate, q to quit."))))) (org-fit-window-to-buffer (get-buffer-window "*Org Help*")) - (setq buffer-read-only nil) - (let ((org-startup-truncated t) - (org-startup-folded nil) - (org-startup-align-all-tables nil)) - (org-mode) - (org-overview)) + (org-overview) (setq buffer-read-only t) (if (and (boundp 'org-goto-start-pos) (integer-or-marker-p org-goto-start-pos)) @@ -309,4 +285,8 @@ With a prefix argument, use the alternative interface: e.g., if (provide 'org-goto) +;; Local variables: +;; generated-autoload-file: "org-loaddefs.el" +;; End: + ;;; org-goto.el ends here diff --git a/lisp/org/org-habit.el b/lisp/org/org-habit.el index e1d13b8325c..f76f0f2131a 100644 --- a/lisp/org/org-habit.el +++ b/lisp/org/org-habit.el @@ -90,7 +90,7 @@ It will be green even if it was done after the deadline." :type 'boolean) (defcustom org-habit-scheduled-past-days nil - "Value to use instead of `org-scheduled-past-days', for habits only. +"Value to use instead of `org-scheduled-past-days', for habits only. If nil, `org-scheduled-past-days' is used. @@ -343,7 +343,10 @@ current time." (if (and in-the-past-p (not last-done-date) (not (< scheduled now))) - '(org-habit-clear-face . org-habit-clear-future-face) + (if (and all-done-dates (= (car all-done-dates) start)) + ;; This is the very first done of this habit. + '(org-habit-ready-face . org-habit-ready-future-face) + '(org-habit-clear-face . org-habit-clear-future-face)) (org-habit-get-faces habit start (and in-the-past-p @@ -409,7 +412,7 @@ current time." 'help-echo (concat (format-time-string (org-time-stamp-format) - (time-add starting (days-to-time (- start (time-to-days starting))))) + (time-add starting (days-to-time (- start (time-to-days starting))))) (if donep " DONE" "")) graph)) (setq start (1+ start) @@ -436,7 +439,7 @@ current time." habit (time-subtract moment (days-to-time org-habit-preceding-days)) moment - (time-add moment (days-to-time org-habit-following-days)))))) + (time-add moment (days-to-time org-habit-following-days)))))) (forward-line))))) (defun org-habit-toggle-habits () diff --git a/lisp/org/org-id.el b/lisp/org/org-id.el index 3efbde04d3f..f8af52964e4 100644 --- a/lisp/org/org-id.el +++ b/lisp/org/org-id.el @@ -71,11 +71,11 @@ ;;; Code: (require 'org) +(require 'org-refile) (require 'ol) (declare-function message-make-fqdn "message" ()) (declare-function org-goto-location "org-goto" (&optional _buf help)) -(declare-function org-link-set-parameters "ol" (type &rest rest)) ;;; Customization @@ -259,6 +259,11 @@ Create an ID if necessary." (interactive) (org-kill-new (org-id-get nil 'create))) +(defvar org-id-overriding-file-name nil + "Tell `org-id-get' to use this as the file name when creating an ID. +This is useful when working with contents in a temporary buffer +that will be copied back to the original.") + ;;;###autoload (defun org-id-get (&optional pom create prefix) "Get the ID property of the entry at point-or-marker POM. @@ -275,7 +280,9 @@ In any case, the ID of the entry is returned." (create (setq id (org-id-new prefix)) (org-entry-put pom "ID" id) - (org-id-add-location id (buffer-file-name (buffer-base-buffer))) + (org-id-add-location id + (or org-id-overriding-file-name + (buffer-file-name (buffer-base-buffer)))) id))))) ;;;###autoload @@ -478,55 +485,64 @@ This will scan all agenda files, all associated archives, and all files currently mentioned in `org-id-locations'. When FILES is given, scan also these files." (interactive) - (if (not org-id-track-globally) - (error "Please turn on `org-id-track-globally' if you want to track IDs") - (let* ((files (delete-dups - (mapcar #'file-truename - (append - ;; Agenda files and all associated archives - (org-agenda-files t org-id-search-archives) - ;; Explicit extra files - (unless (symbolp org-id-extra-files) - org-id-extra-files) - ;; All files known to have IDs - org-id-files - ;; function input - files)))) - (nfiles (length files)) - ids seen-ids (ndup 0) (i 0) file-id-alist) - (with-temp-buffer - (delay-mode-hooks - (org-mode) - (dolist (file files) - (unless silent - (setq i (1+ i)) - (message "Finding ID locations (%d/%d files): %s" - i nfiles file)) - (when (file-exists-p file) - (insert-file-contents file nil nil nil 'replace) - (setq ids (org-map-entries - (lambda () - (org-entry-get (point) "ID")) - "ID<>\"\"")) - (dolist (id ids) - (if (member id seen-ids) - (progn - (message "Duplicate ID \"%s\"" id) - (setq ndup (1+ ndup))) - (push id seen-ids))) + (unless org-id-track-globally + (error "Please turn on `org-id-track-globally' if you want to track IDs")) + (setq org-id-locations nil) + (let* ((files + (delete-dups + (mapcar #'file-truename + (cl-remove-if-not + ;; Default `org-id-extra-files' value contains + ;; `agenda-archives' symbol. + #'stringp + (append + ;; Agenda files and all associated archives. + (org-agenda-files t org-id-search-archives) + ;; Explicit extra files. + (if (symbolp org-id-extra-files) + (symbol-value org-id-extra-files) + org-id-extra-files) + ;; All files known to have IDs. + org-id-files + ;; Additional files from function call. + files))))) + (nfiles (length files)) + (id-regexp + (rx (seq bol (0+ (any "\t ")) ":ID:" (1+ " ") (not (any " "))))) + (seen-ids nil) + (ndup 0) + (i 0)) + (dolist (file files) + (when (file-exists-p file) + (unless silent + (cl-incf i) + (message "Finding ID locations (%d/%d files): %s" i nfiles file)) + (with-current-buffer (find-file-noselect file t) + (let ((ids nil) + (case-fold-search t)) + (org-with-point-at 1 + (while (re-search-forward id-regexp nil t) + (when (org-at-property-p) + (push (org-entry-get (point) "ID") ids))) (when ids - (setq file-id-alist (cons (cons (abbreviate-file-name file) ids) - file-id-alist))))))) - (setq org-id-locations file-id-alist) - (setq org-id-files (mapcar 'car org-id-locations)) - (org-id-locations-save) - ;; now convert to a hash - (setq org-id-locations (org-id-alist-to-hash org-id-locations)) - (when (> ndup 0) - (warn "WARNING: %d duplicate IDs found, check *Messages* buffer" ndup)) - (message "%d files scanned, %d files contains IDs and in total %d IDs found." - nfiles (length org-id-files) (hash-table-count org-id-locations)) - org-id-locations))) + (push (cons (abbreviate-file-name file) ids) + org-id-locations) + (dolist (id ids) + (cond + ((not (member id seen-ids)) (push id seen-ids)) + (silent nil) + (t + (message "Duplicate ID %S" id) + (cl-incf ndup)))))))))) + (setq org-id-files (mapcar #'car org-id-locations)) + (org-id-locations-save) + ;; Now convert to a hash table. + (setq org-id-locations (org-id-alist-to-hash org-id-locations)) + (when (and (not silent) (> ndup 0)) + (warn "WARNING: %d duplicate IDs found, check *Messages* buffer" ndup)) + (message "%d files scanned, %d files contains IDs, and %d IDs found." + nfiles (length org-id-files) (hash-table-count org-id-locations)) + org-id-locations)) (defun org-id-locations-save () "Save `org-id-locations' in `org-id-locations-file'." @@ -572,8 +588,10 @@ When FILES is given, scan also these files." (defun org-id-add-location (id file) "Add the ID with location FILE to the database of ID locations." ;; Only if global tracking is on, and when the buffer has a file + (unless file + (error "bug: org-id-get expects a file-visiting buffer")) (let ((afile (abbreviate-file-name file))) - (when (and org-id-track-globally id file) + (when (and org-id-track-globally id) (unless org-id-locations (org-id-locations-load)) (puthash id afile org-id-locations) (unless (member afile org-id-files) @@ -631,7 +649,7 @@ When FILES is given, scan also these files." (or (and org-id-locations (hash-table-p org-id-locations) (gethash id org-id-locations)) - ;; ball back on current buffer + ;; Fall back on current buffer (buffer-file-name (or (buffer-base-buffer (current-buffer)) (current-buffer))))) @@ -665,8 +683,11 @@ optional argument MARKERP, return the position as a new marker." (let* ((link (concat "id:" (org-id-get-create))) (case-fold-search nil) (desc (save-excursion - (org-back-to-heading t) - (or (and (looking-at org-complex-heading-regexp) + (org-back-to-heading-or-point-min t) + (or (and (org-before-first-heading-p) + (file-name-nondirectory + (buffer-file-name (buffer-base-buffer)))) + (and (looking-at org-complex-heading-regexp) (if (match-end 4) (match-string 4) (match-string 0))) @@ -674,7 +695,7 @@ optional argument MARKERP, return the position as a new marker." (org-link-store-props :link link :description desc :type "id") link))) -(defun org-id-open (id) +(defun org-id-open (id _) "Go to the entry with id ID." (org-mark-ring-push) (let ((m (org-id-find id 'marker)) diff --git a/lisp/org/org-indent.el b/lisp/org/org-indent.el index 5171919465b..73b077965c4 100644 --- a/lisp/org/org-indent.el +++ b/lisp/org/org-indent.el @@ -71,8 +71,6 @@ Delay used when the buffer to initialize isn't current.") (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 - "Used locally.") (defvar org-indent-modified-headline-flag nil "Non-nil means the last deletion operated on a headline. It is modified by `org-indent-notify-modified-headline'.") @@ -178,10 +176,11 @@ during idle time." (setq-local indent-tabs-mode nil) (setq-local org-indent--initial-marker (copy-marker 1)) (when org-indent-mode-turns-off-org-adapt-indentation - (setq-local org-adapt-indentation nil)) + ;; Don't turn off `org-adapt-indentation' when its value is + ;; 'headline-data, just indent headline data specially. + (or (eq org-adapt-indentation 'headline-data) + (setq-local org-adapt-indentation nil))) (when org-indent-mode-turns-on-hiding-stars - (setq-local org-hide-leading-stars-before-indent-mode - org-hide-leading-stars) (setq-local org-hide-leading-stars t)) (org-indent--compute-prefixes) (if (boundp 'filter-buffer-substring-functions) @@ -207,15 +206,14 @@ during idle time." (setq org-indent-agent-timer (run-with-idle-timer 0.2 t #'org-indent-initialize-agent)))) (t - ;; mode was turned off (or we refused to turn it on) + ;; Mode was turned off (or we refused to turn it on) (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 (boundp 'org-hide-leading-stars-before-indent-mode) - (setq-local org-hide-leading-stars - org-hide-leading-stars-before-indent-mode)) + (when (local-variable-p 'org-hide-leading-stars) + (kill-local-variable 'org-hide-leading-stars)) (if (boundp 'filter-buffer-substring-functions) (remove-hook 'filter-buffer-substring-functions (lambda (fun start end delete) @@ -365,7 +363,18 @@ stopped." level (org-list-item-body-column (point)))) ;; Regular line. (t - (org-indent-set-line-properties level (current-indentation)))))))))) + (org-indent-set-line-properties + level + (current-indentation) + ;; When adapt indentation is 'headline-data, use + ;; `org-indent--heading-line-prefixes' for setting + ;; headline data indentation. + (and (eq org-adapt-indentation 'headline-data) + (or (org-at-planning-p) + (org-at-clock-log-p) + (looking-at-p org-property-start-re) + (looking-at-p org-property-end-re) + (looking-at-p org-property-re)))))))))))) (defun org-indent-notify-modified-headline (beg end) "Set `org-indent-modified-headline-flag' depending on context. diff --git a/lisp/org/org-keys.el b/lisp/org/org-keys.el index 4d4e1241c5a..37df2998323 100644 --- a/lisp/org/org-keys.el +++ b/lisp/org/org-keys.el @@ -56,7 +56,7 @@ (declare-function org-clone-subtree-with-time-shift "org" (n &optional shift)) (declare-function org-columns "org" (&optional global columns-fmt-string)) (declare-function org-comment-dwim "org" (arg)) -(declare-function org-copy "org" ()) +(declare-function org-refile-copy "org" ()) (declare-function org-copy-special "org" ()) (declare-function org-copy-visible "org" (beg end)) (declare-function org-ctrl-c-ctrl-c "org" (&optional arg)) @@ -148,7 +148,7 @@ (declare-function org-remove-file "org" (&optional file)) (declare-function org-resolve-clocks "org" (&optional only-dangling-p prompt-fn last-valid)) (declare-function org-return "org" (&optional indent)) -(declare-function org-return-indent "org" ()) +(declare-function org-return-and-maybe-indent "org" ()) (declare-function org-reveal "org" (&optional siblings)) (declare-function org-schedule "org" (arg &optional time)) (declare-function org-self-insert-command "org" (N)) @@ -196,6 +196,7 @@ (declare-function org-todo "org" (&optional arg1)) (declare-function org-toggle-archive-tag "org" (&optional find-done)) (declare-function org-toggle-checkbox "org" (&optional toggle-presence)) +(declare-function org-toggle-radio-button "org" (&optional arg)) (declare-function org-toggle-comment "org" ()) (declare-function org-toggle-fixed-width "org" ()) (declare-function org-toggle-inline-images "org" (&optional include-linked)) @@ -218,7 +219,7 @@ ;;; Variables (defvar org-mode-map (make-sparse-keymap) - "Keymap fo Org mode.") + "Keymap for Org mode.") (defvaralias 'org-CUA-compatible 'org-replace-disputed-keys) @@ -444,7 +445,7 @@ COMMANDS is a list of alternating OLDDEF NEWDEF command names." ;;;; TAB key with modifiers (org-defkey org-mode-map (kbd "C-i") #'org-cycle) (org-defkey org-mode-map (kbd "") #'org-cycle) -(org-defkey org-mode-map (kbd "C-") #'org-force-cycle-archived) +(org-defkey org-mode-map (kbd "C-c C-") #'org-force-cycle-archived) ;; Override text-mode binding to expose `complete-symbol' for ;; pcomplete functionality. (org-defkey org-mode-map (kbd "M-") nil) @@ -580,7 +581,7 @@ COMMANDS is a list of alternating OLDDEF NEWDEF command names." (org-defkey org-mode-map (kbd "C-c C-d") #'org-deadline) (org-defkey org-mode-map (kbd "C-c ;") #'org-toggle-comment) (org-defkey org-mode-map (kbd "C-c C-w") #'org-refile) -(org-defkey org-mode-map (kbd "C-c M-w") #'org-copy) +(org-defkey org-mode-map (kbd "C-c M-w") #'org-refile-copy) (org-defkey org-mode-map (kbd "C-c /") #'org-sparse-tree) ;minor-mode reserved (org-defkey org-mode-map (kbd "C-c \\") #'org-match-sparse-tree) ;minor-mode r. (org-defkey org-mode-map (kbd "C-c RET") #'org-ctrl-c-ret) @@ -617,7 +618,7 @@ COMMANDS is a list of alternating OLDDEF NEWDEF command names." (org-defkey org-mode-map (kbd "C-c C-k") #'org-kill-note-or-show-branches) (org-defkey org-mode-map (kbd "C-c #") #'org-update-statistics-cookies) (org-defkey org-mode-map (kbd "RET") #'org-return) -(org-defkey org-mode-map (kbd "C-j") #'org-return-indent) +(org-defkey org-mode-map (kbd "C-j") #'org-return-and-maybe-indent) (org-defkey org-mode-map (kbd "C-c ?") #'org-table-field-info) (org-defkey org-mode-map (kbd "C-c SPC") #'org-table-blank-field) (org-defkey org-mode-map (kbd "C-c +") #'org-table-sum) @@ -658,6 +659,7 @@ COMMANDS is a list of alternating OLDDEF NEWDEF command names." (org-defkey org-mode-map (kbd "C-c C-x C-M-v") #'org-redisplay-inline-images) (org-defkey org-mode-map (kbd "C-c C-x \\") #'org-toggle-pretty-entities) (org-defkey org-mode-map (kbd "C-c C-x C-b") #'org-toggle-checkbox) +(org-defkey org-mode-map (kbd "C-c C-x C-r") #'org-toggle-radio-button) (org-defkey org-mode-map (kbd "C-c C-x p") #'org-set-property) (org-defkey org-mode-map (kbd "C-c C-x P") #'org-set-property-and-value) (org-defkey org-mode-map (kbd "C-c C-x e") #'org-set-effort) @@ -923,6 +925,10 @@ a-list placed behind the generic `org-babel-key-prefix'.") (interactive) (describe-bindings org-babel-key-prefix)) - (provide 'org-keys) + +;; Local variables: +;; generated-autoload-file: "org-loaddefs.el" +;; End: + ;;; org-keys.el ends here diff --git a/lisp/org/org-lint.el b/lisp/org/org-lint.el index 5be1ec72863..e4e0ef75166 100644 --- a/lisp/org/org-lint.el +++ b/lisp/org/org-lint.el @@ -108,6 +108,7 @@ (require 'cl-lib) (require 'ob) (require 'ol) +(require 'org-attach) (require 'org-macro) (require 'ox) @@ -423,8 +424,10 @@ instead" (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)) + ;; DIR is also used for attachments. + (delete "dir" + (mapcar (lambda (arg) (downcase (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) @@ -541,15 +544,16 @@ Use :header-args: instead" (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")))))))) + (let ((headline? (org-element-lineage d '(headline))) + (before + (mapcar #'org-element-type + (assq d (reverse (org-element-contents + (org-element-property :parent d))))))) + (list (org-element-property :post-affiliated d) + (if (or (and headline? (member before '(nil (planning)))) + (and (null headline?) (member before '(nil (comment))))) + "Incorrect contents for PROPERTIES drawer" + "Incorrect location for PROPERTIES drawer"))))))) (defun org-lint-invalid-effort-property (ast) (org-element-map ast 'node-property @@ -564,16 +568,23 @@ Use :header-args: instead" (defun org-lint-link-to-local-file (ast) (org-element-map ast 'link (lambda (l) - (when (equal "file" (org-element-property :type l)) - (let ((file (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)))))))) + (let ((type (org-element-property :type l))) + (pcase type + ((or "attachment" "file") + (let* ((path (org-element-property :path l)) + (file (if (string= type "file") + path + (org-with-point-at (org-element-property :begin l) + (org-attach-expand path))))) + (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 description" + "Link to non-existent local file %S") + file))))) + (_ nil)))))) (defun org-lint-non-existent-setupfile-parameter (ast) (org-element-map ast 'keyword @@ -793,15 +804,25 @@ Use \"export %s\" instead" (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) + (`drawer + ;; Find drawer opening lines within non-empty drawers. + (let ((end (org-element-property :contents-end element))) + (when end + (while (re-search-forward org-drawer-regexp end t) + (let ((n (org-trim (match-string-no-properties 0)))) + (push (list (line-beginning-position) + (format "Possible misleading drawer entry %S" n)) + reports)))) + (goto-char (org-element-property :end element)))) + (`property-drawer + (goto-char (org-element-property :end element))) ((or `comment-block `example-block `export-block `src-block `verse-block) nil) (_ + ;; Find drawer opening lines outside of any drawer. (push (list (line-beginning-position) - (format "Possible incomplete drawer \"%s\"" name)) + (format "Possible incomplete drawer %S" name)) reports))))) reports)) @@ -1257,6 +1278,10 @@ ARG can also be a list of checker names, as symbols, to run." (org-lint--display-reports (current-buffer) checkers) (message "Org linting process completed")))) - (provide 'org-lint) + +;; Local variables: +;; generated-autoload-file: "org-loaddefs.el" +;; End: + ;;; org-lint.el ends here diff --git a/lisp/org/org-list.el b/lisp/org/org-list.el index c79325f1f33..b8383283be8 100644 --- a/lisp/org/org-list.el +++ b/lisp/org/org-list.el @@ -81,12 +81,12 @@ (require 'org-compat) (defvar org-M-RET-may-split-line) +(defvar org-adapt-indentation) (defvar org-auto-align-tags) (defvar org-blank-before-new-entry) (defvar org-clock-string) (defvar org-closed-string) (defvar org-deadline-string) -(defvar org-description-max-indent) (defvar org-done-keywords) (defvar org-drawer-regexp) (defvar org-element-all-objects) @@ -911,13 +911,13 @@ items, as returned by `org-list-prevs-alist'." STRUCT is the list structure." (let* ((item-end (org-list-get-item-end item struct)) (sub-struct (cdr (member (assq item struct) struct))) - subtree) - (catch 'exit - (mapc (lambda (e) - (let ((pos (car e))) - (if (< pos item-end) (push pos subtree) (throw 'exit nil)))) - sub-struct)) - (nreverse subtree))) + items) + (catch :exit + (pcase-dolist (`(,pos . ,_) sub-struct) + (if (< pos item-end) + (push pos items) + (throw :exit nil)))) + (nreverse items))) (defun org-list-get-all-items (item struct prevs) "List all items in the same sub-list as ITEM. @@ -1234,125 +1234,127 @@ after the bullet. Cursor will be after this text once the function ends. This function modifies STRUCT." - (let ((case-fold-search t)) - ;; 1. Get information about list: ITEM containing POS, position of - ;; point with regards to item start (BEFOREP), blank lines - ;; number separating items (BLANK-NB), if we're allowed to - ;; (SPLIT-LINE-P). - (let* ((item (goto-char (catch :exit - (let ((inner-item 0)) - (pcase-dolist (`(,i . ,_) struct) - (cond - ((= i pos) (throw :exit i)) - ((< i pos) (setq inner-item i)) - (t (throw :exit inner-item)))) - inner-item)))) - (item-end (org-list-get-item-end item struct)) - (item-end-no-blank (org-list-get-item-end-before-blank item struct)) - (beforep - (progn - (looking-at org-list-full-item-re) - (<= 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)) - ;; 2. Build the new item to be created. Concatenate same - ;; bullet as item, checkbox, text AFTER-BULLET if - ;; provided, and text cut from point to end of item - ;; (TEXT-CUT) to form item's BODY. TEXT-CUT depends on - ;; BEFOREP and SPLIT-LINE-P. The difference of size - ;; between what was cut and what was inserted in buffer - ;; is stored in SIZE-OFFSET. - (ind (org-list-get-ind item struct)) - (ind-size (if indent-tabs-mode - (+ (/ ind tab-width) (mod ind tab-width)) - ind)) - (bullet (org-list-bullet-string (org-list-get-bullet item struct))) - (box (when checkbox "[ ]")) - (text-cut - (and (not beforep) split-line-p - (progn - (goto-char pos) - ;; If POS is greater than ITEM-END, then point is - ;; in some white lines after the end of the list. - ;; Those must be removed, or they will be left, - ;; stacking up after the list. - (when (< item-end pos) - (delete-region (1- item-end) (point-at-eol))) - (skip-chars-backward " \r\t\n") - (setq pos (point)) - (delete-and-extract-region pos item-end-no-blank)))) - (body (concat bullet (when box (concat box " ")) after-bullet - (and text-cut - (if (string-match "\\`[ \t]+" text-cut) - (replace-match "" t t text-cut) - text-cut)))) - (item-sep (make-string (1+ blank-nb) ?\n)) - (item-size (+ ind-size (length body) (length item-sep))) - (size-offset (- item-size (length text-cut)))) - ;; 4. Insert effectively item into buffer. - (goto-char item) - (indent-to-column ind) - (insert body item-sep) - ;; 5. Add new item to STRUCT. - (mapc (lambda (e) - (let ((p (car e)) (end (nth 6 e))) - (cond - ;; Before inserted item, positions don't change but - ;; an item ending after insertion has its end shifted - ;; by SIZE-OFFSET. - ((< p item) - (when (> end item) (setcar (nthcdr 6 e) (+ end size-offset)))) - ;; Trivial cases where current item isn't split in - ;; two. Just shift every item after new one by - ;; ITEM-SIZE. - ((or beforep (not split-line-p)) - (setcar e (+ p item-size)) - (setcar (nthcdr 6 e) (+ end item-size))) - ;; Item is split in two: elements before POS are just - ;; shifted by ITEM-SIZE. In the case item would end - ;; after split POS, ending is only shifted by - ;; SIZE-OFFSET. - ((< p pos) - (setcar e (+ p item-size)) - (if (< end pos) - (setcar (nthcdr 6 e) (+ end item-size)) - (setcar (nthcdr 6 e) (+ end size-offset)))) - ;; Elements after POS are moved into new item. - ;; Length of ITEM-SEP has to be removed as ITEM-SEP - ;; doesn't appear in buffer yet. - ((< p item-end) - (setcar e (+ p size-offset (- item pos (length item-sep)))) - (if (= end item-end) - (setcar (nthcdr 6 e) (+ item item-size)) - (setcar (nthcdr 6 e) - (+ end size-offset - (- item pos (length item-sep)))))) - ;; Elements at ITEM-END or after are only shifted by - ;; SIZE-OFFSET. - (t (setcar e (+ p size-offset)) - (setcar (nthcdr 6 e) (+ end size-offset)))))) - struct) - (push (list item ind bullet nil box nil (+ item item-size)) struct) - (setq struct (sort struct (lambda (e1 e2) (< (car e1) (car e2))))) - ;; 6. If not BEFOREP, new item must appear after ITEM, so - ;; exchange ITEM with the next item in list. Position cursor - ;; after bullet, counter, checkbox, and label. - (if beforep - (goto-char item) - (setq struct (org-list-swap-items item (+ item item-size) struct)) - (goto-char (org-list-get-next-item - item struct (org-list-prevs-alist struct)))) - struct))) + (let* ((case-fold-search t) + ;; Get information about list: ITEM containing POS, position + ;; of point with regards to item start (BEFOREP), blank lines + ;; number separating items (BLANK-NB), if we're allowed to + ;; (SPLIT-LINE-P). + (item + (catch :exit + (let ((i nil)) + (pcase-dolist (`(,start ,_ ,_ ,_ ,_ ,_ ,end) struct) + (cond + ((> start pos) (throw :exit i)) + ((< end pos) nil) ;skip sub-lists before point + (t (setq i start)))) + ;; If no suitable item is found, insert a sibling of the + ;; last item in buffer. + (or i (caar (reverse struct)))))) + (item-end (org-list-get-item-end item struct)) + (item-end-no-blank (org-list-get-item-end-before-blank item struct)) + (beforep + (progn + (goto-char item) + (looking-at org-list-full-item-re) + (<= 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)) + ;; Build the new item to be created. Concatenate same bullet + ;; as item, checkbox, text AFTER-BULLET if provided, and text + ;; cut from point to end of item (TEXT-CUT) to form item's + ;; BODY. TEXT-CUT depends on BEFOREP and SPLIT-LINE-P. The + ;; difference of size between what was cut and what was + ;; inserted in buffer is stored in SIZE-OFFSET. + (ind (org-list-get-ind item struct)) + (ind-size (if indent-tabs-mode + (+ (/ ind tab-width) (mod ind tab-width)) + ind)) + (bullet (org-list-bullet-string (org-list-get-bullet item struct))) + (box (and checkbox "[ ]")) + (text-cut + (and (not beforep) + split-line-p + (progn + (goto-char pos) + ;; If POS is greater than ITEM-END, then point is in + ;; some white lines after the end of the list. Those + ;; must be removed, or they will be left, stacking up + ;; after the list. + (when (< item-end pos) + (delete-region (1- item-end) (point-at-eol))) + (skip-chars-backward " \r\t\n") + ;; Cut position is after any blank on the line. + (save-excursion + (skip-chars-forward " \t") + (setq pos (point))) + (delete-and-extract-region (point) item-end-no-blank)))) + (body + (concat bullet + (and box (concat box " ")) + after-bullet + (and text-cut + (if (string-match "\\`[ \t]+" text-cut) + (replace-match "" t t text-cut) + text-cut)))) + (item-sep (make-string (1+ blank-nb) ?\n)) + (item-size (+ ind-size (length body) (length item-sep))) + (size-offset (- item-size (length text-cut)))) + ;; Insert effectively item into buffer. + (goto-char item) + (indent-to-column ind) + (insert body item-sep) + ;; Add new item to STRUCT. + (dolist (e struct) + (let ((p (car e)) (end (nth 6 e))) + (cond + ;; Before inserted item, positions don't change but an item + ;; ending after insertion has its end shifted by SIZE-OFFSET. + ((< p item) + (when (> end item) + (setcar (nthcdr 6 e) (+ end size-offset)))) + ;; Item where insertion happens may be split in two parts. + ;; In this case, move start by ITEM-SIZE and end by + ;; SIZE-OFFSET. + ((and (= p item) (not beforep) split-line-p) + (setcar e (+ p item-size)) + (setcar (nthcdr 6 e) (+ end size-offset))) + ;; Items starting after modified item fall into two + ;; categories. + ;; + ;; If modified item was split, and current sub-item was + ;; located after split point, it was moved to the new item: + ;; the part between body start and split point (POS) was + ;; removed. So we compute the length of that part and shift + ;; item's positions accordingly. + ;; + ;; Otherwise, the item was simply shifted by SIZE-OFFSET. + ((and split-line-p (not beforep) (>= p pos) (<= p item-end-no-blank)) + (let ((offset (- pos item ind (length bullet) (length after-bullet)))) + (setcar e (- p offset)) + (setcar (nthcdr 6 e) (- end offset)))) + (t + (setcar e (+ p size-offset)) + (setcar (nthcdr 6 e) (+ end size-offset)))))) + (push (list item ind bullet nil box nil (+ item item-size)) struct) + (setq struct (sort struct #'car-less-than-car)) + ;; If not BEFOREP, new item must appear after ITEM, so exchange + ;; ITEM with the next item in list. Position cursor after bullet, + ;; counter, checkbox, and label. + (if beforep + (goto-char item) + (setq struct (org-list-swap-items item (+ item item-size) struct)) + (goto-char (org-list-get-next-item + item struct (org-list-prevs-alist struct)))) + struct)) (defun org-list-delete-item (item struct) "Remove ITEM from the list and return the new structure. @@ -1793,10 +1795,9 @@ This function modifies STRUCT." ;; There are boxes checked after an unchecked one: fix that. (when (member "[X]" after-unchecked) (let ((index (- (length struct) (length after-unchecked)))) - (mapc (lambda (e) - (when (org-list-get-checkbox e struct) - (org-list-set-checkbox e struct "[ ]"))) - (nthcdr index all-items)) + (dolist (e (nthcdr index all-items)) + (when (org-list-get-checkbox e struct) + (org-list-set-checkbox e struct "[ ]"))) ;; Verify once again the structure, without ORDERED. (org-list-struct-fix-box struct parents prevs nil) ;; Return blocking item. @@ -1807,24 +1808,22 @@ This function modifies STRUCT." This function modifies STRUCT." (let (end-list acc-end) - (mapc (lambda (e) - (let* ((pos (car e)) - (ind-pos (org-list-get-ind pos struct)) - (end-pos (org-list-get-item-end pos struct))) - (unless (assq end-pos struct) - ;; To determine real ind of an ending position that is - ;; not at an item, we have to find the item it 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 '>))) - (push (cons - ;; Else part is for the bottom point. - (if item-up (+ (org-list-get-ind item-up struct) 2) 0) - end-pos) - end-list))) - (push (cons ind-pos pos) end-list) - (push (cons end-pos pos) acc-end))) - struct) + (pcase-dolist (`(,pos . ,_) struct) + (let ((ind-pos (org-list-get-ind pos struct)) + (end-pos (org-list-get-item-end pos struct))) + (unless (assq end-pos struct) + ;; To determine real ind of an ending position that is not + ;; at an item, we have to find the item it 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 #'>))) + (push (cons + ;; Else part is for the bottom point. + (if item-up (+ (org-list-get-ind item-up struct) 2) 0) + end-pos) + end-list))) + (push (cons ind-pos pos) end-list) + (push (cons end-pos pos) acc-end))) (setq end-list (sort end-list (lambda (e1 e2) (< (cdr e1) (cdr e2))))) (org-list-struct-assoc-end struct end-list))) @@ -2021,10 +2020,9 @@ beginning of the item." (item (copy-marker (point-at-bol))) (all (org-list-get-all-items (marker-position item) struct prevs)) (value init-value)) - (mapc (lambda (e) - (goto-char e) - (setq value (apply function value args))) - (nreverse all)) + (dolist (e (nreverse all)) + (goto-char e) + (setq value (apply function value args))) (goto-char item) (move-marker item nil) value)) @@ -2046,9 +2044,8 @@ Possible values are: `folded', `children' or `subtree'. See ;; Then fold every child. (let* ((parents (org-list-parents-alist struct)) (children (org-list-get-children item struct parents))) - (mapc (lambda (e) - (org-list-set-item-visibility e struct 'folded)) - children))) + (dolist (child children) + (org-list-set-item-visibility child struct 'folded)))) ((eq view 'subtree) ;; Show everything (let ((item-end (org-list-get-item-end item struct))) @@ -2303,6 +2300,56 @@ is an integer, 0 means `-', 1 means `+' etc. If WHICH is (org-list-struct-fix-ind struct parents) (org-list-struct-apply-struct struct old-struct))))) +;;;###autoload +(define-minor-mode org-list-checkbox-radio-mode + "When turned on, use list checkboxes as radio buttons." + nil " CheckBoxRadio" nil + (unless (eq major-mode 'org-mode) + (user-error "Cannot turn this mode outside org-mode buffers"))) + +(defun org-toggle-radio-button (&optional arg) + "Toggle off all checkboxes and toggle on the one at point." + (interactive "P") + (if (not (org-at-item-p)) + (user-error "Cannot toggle checkbox outside of a list") + (let* ((cpos (org-in-item-p)) + (struct (org-list-struct)) + (orderedp (org-entry-get nil "ORDERED")) + (parents (org-list-parents-alist struct)) + (old-struct (copy-tree struct)) + (cbox (org-list-get-checkbox cpos struct)) + (prevs (org-list-prevs-alist struct)) + (start (org-list-get-list-begin (point-at-bol) struct prevs)) + (new (unless (and cbox (equal arg '(4)) (equal start cpos)) + "[ ]"))) + (dolist (pos (org-list-get-all-items + start struct (org-list-prevs-alist struct))) + (org-list-set-checkbox pos struct new)) + (when new + (org-list-set-checkbox + cpos struct + (cond ((equal arg '(4)) (unless cbox "[ ]")) + ((equal arg '(16)) (unless cbox "[-]")) + (t (if (equal cbox "[X]") "[ ]" "[X]"))))) + (org-list-struct-fix-box struct parents prevs orderedp) + (org-list-struct-apply-struct struct old-struct) + (org-update-checkbox-count-maybe)))) + +(defun org-at-radio-list-p () + "Is point at a list item with radio buttons?" + (when (org-match-line (org-item-re)) ;short-circuit + (let* ((e (save-excursion (beginning-of-line) (org-element-at-point)))) + ;; Check we're really on a line with a bullet. + (when (memq (org-element-type e) '(item plain-list)) + ;; Look for ATTR_ORG attribute in the current plain list. + (let ((plain-list (org-element-lineage e '(plain-list) t))) + (org-with-point-at (org-element-property :post-affiliated plain-list) + (let ((case-fold-search t) + (regexp "^[ \t]*#\\+attr_org:.* :radio \\(\\S-+\\)") + (begin (org-element-property :begin plain-list))) + (and (re-search-backward regexp begin t) + (not (string-equal "nil" (match-string 1))))))))))) + (defun org-toggle-checkbox (&optional toggle-presence) "Toggle the checkbox in the current line. @@ -2317,92 +2364,94 @@ If point is on a headline, apply this to all checkbox items in the text below the heading, taking as reference the first item in subtree, ignoring planning line and any drawer following it." (interactive "P") - (save-excursion - (let* (singlep - block-item - lim-up - lim-down - (orderedp (org-entry-get nil "ORDERED")) - (_bounds - ;; In a region, start at first item in region. + (if (org-at-radio-list-p) + (org-toggle-radio-button toggle-presence) + (save-excursion + (let* (singlep + block-item + lim-up + lim-down + (orderedp (org-entry-get nil "ORDERED")) + (_bounds + ;; In a region, start at first item in region. + (cond + ((org-region-active-p) + (let ((limit (region-end))) + (goto-char (region-beginning)) + (if (org-list-search-forward (org-item-beginning-re) limit t) + (setq lim-up (point-at-bol)) + (error "No item in region")) + (setq lim-down (copy-marker limit)))) + ((org-at-heading-p) + ;; On a heading, start at first item after drawers and + ;; time-stamps (scheduled, etc.). + (let ((limit (save-excursion (outline-next-heading) (point)))) + (org-end-of-meta-data t) + (if (org-list-search-forward (org-item-beginning-re) limit t) + (setq lim-up (point-at-bol)) + (error "No item in subtree")) + (setq lim-down (copy-marker limit)))) + ;; Just one item: set SINGLEP flag. + ((org-at-item-p) + (setq singlep t) + (setq lim-up (point-at-bol) + lim-down (copy-marker (point-at-eol)))) + (t (error "Not at an item or heading, and no active region")))) + ;; Determine the checkbox going to be applied to all items + ;; within bounds. + (ref-checkbox + (progn + (goto-char lim-up) + (let ((cbox (and (org-at-item-checkbox-p) (match-string 1)))) + (cond + ((equal toggle-presence '(16)) "[-]") + ((equal toggle-presence '(4)) + (unless cbox "[ ]")) + ((equal "[X]" cbox) "[ ]") + (t "[X]")))))) + ;; When an item is found within bounds, grab the full list at + ;; point structure, then: (1) set check-box of all its items + ;; within bounds to REF-CHECKBOX, (2) fix check-boxes of the + ;; whole list, (3) move point after the list. + (goto-char lim-up) + (while (and (< (point) lim-down) + (org-list-search-forward (org-item-beginning-re) + lim-down 'move)) + (let* ((struct (org-list-struct)) + (struct-copy (copy-tree struct)) + (parents (org-list-parents-alist struct)) + (prevs (org-list-prevs-alist struct)) + (bottom (copy-marker (org-list-get-bottom-point struct))) + (items-to-toggle (cl-remove-if + (lambda (e) (or (< e lim-up) (> e lim-down))) + (mapcar #'car struct)))) + (dolist (e items-to-toggle) + (org-list-set-checkbox + e struct + ;; If there is no box at item, leave as-is unless + ;; function was called with C-u prefix. + (let ((cur-box (org-list-get-checkbox e struct))) + (if (or cur-box (equal toggle-presence '(4))) + ref-checkbox + cur-box)))) + (setq block-item (org-list-struct-fix-box + struct parents prevs orderedp)) + ;; Report some problems due to ORDERED status of subtree. + ;; If only one box was being checked, throw an error, else, + ;; only signal problems. (cond - ((org-region-active-p) - (let ((limit (region-end))) - (goto-char (region-beginning)) - (if (org-list-search-forward (org-item-beginning-re) limit t) - (setq lim-up (point-at-bol)) - (error "No item in region")) - (setq lim-down (copy-marker limit)))) - ((org-at-heading-p) - ;; On a heading, start at first item after drawers and - ;; time-stamps (scheduled, etc.). - (let ((limit (save-excursion (outline-next-heading) (point)))) - (org-end-of-meta-data t) - (if (org-list-search-forward (org-item-beginning-re) limit t) - (setq lim-up (point-at-bol)) - (error "No item in subtree")) - (setq lim-down (copy-marker limit)))) - ;; Just one item: set SINGLEP flag. - ((org-at-item-p) - (setq singlep t) - (setq lim-up (point-at-bol) - lim-down (copy-marker (point-at-eol)))) - (t (error "Not at an item or heading, and no active region")))) - ;; Determine the checkbox going to be applied to all items - ;; within bounds. - (ref-checkbox - (progn - (goto-char lim-up) - (let ((cbox (and (org-at-item-checkbox-p) (match-string 1)))) - (cond - ((equal toggle-presence '(16)) "[-]") - ((equal toggle-presence '(4)) - (unless cbox "[ ]")) - ((equal "[X]" cbox) "[ ]") - (t "[X]")))))) - ;; When an item is found within bounds, grab the full list at - ;; point structure, then: (1) set check-box of all its items - ;; within bounds to REF-CHECKBOX, (2) fix check-boxes of the - ;; whole list, (3) move point after the list. - (goto-char lim-up) - (while (and (< (point) lim-down) - (org-list-search-forward (org-item-beginning-re) - lim-down 'move)) - (let* ((struct (org-list-struct)) - (struct-copy (copy-tree struct)) - (parents (org-list-parents-alist struct)) - (prevs (org-list-prevs-alist struct)) - (bottom (copy-marker (org-list-get-bottom-point struct))) - (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 - e struct - ;; If there is no box at item, leave as-is - ;; unless function was called with C-u prefix. - (let ((cur-box (org-list-get-checkbox e struct))) - (if (or cur-box (equal toggle-presence '(4))) - ref-checkbox - cur-box)))) - items-to-toggle) - (setq block-item (org-list-struct-fix-box - struct parents prevs orderedp)) - ;; Report some problems due to ORDERED status of subtree. - ;; If only one box was being checked, throw an error, else, - ;; only signal problems. - (cond - ((and singlep block-item (> lim-up block-item)) - (error - "Checkbox blocked because of unchecked box at line %d" - (org-current-line block-item))) - (block-item - (message - "Checkboxes were removed due to unchecked box at line %d" - (org-current-line block-item)))) - (goto-char bottom) - (move-marker bottom nil) - (org-list-struct-apply-struct struct struct-copy))) - (move-marker lim-down nil))) + ((and singlep block-item (> lim-up block-item)) + (error + "Checkbox blocked because of unchecked box at line %d" + (org-current-line block-item))) + (block-item + (message + "Checkboxes were removed due to unchecked box at line %d" + (org-current-line block-item)))) + (goto-char bottom) + (move-marker bottom nil) + (org-list-struct-apply-struct struct struct-copy))) + (move-marker lim-down nil)))) (org-update-checkbox-count-maybe)) (defun org-reset-checkbox-state-subtree () @@ -2632,10 +2681,9 @@ Return t if successful." (org-list-bullet-string "-"))) ;; Shift every item by OFFSET and fix bullets. Then ;; apply changes to buffer. - (mapc (lambda (e) - (let ((ind (org-list-get-ind (car e) struct))) - (org-list-set-ind (car e) struct (+ ind offset)))) - struct) + (pcase-dolist (`(,pos . ,_) struct) + (let ((ind (org-list-get-ind pos struct))) + (org-list-set-ind pos struct (+ ind offset)))) (org-list-struct-fix-bul struct prevs) (org-list-struct-apply-struct struct old-struct)))) ;; Forbidden move: @@ -2733,51 +2781,83 @@ If a region is active, all items inside will be moved." (t (error "Not at an item"))))) (defvar org-tab-ind-state) -(defvar org-adapt-indentation) (defun org-cycle-item-indentation () "Cycle levels of indentation of an empty item. + The first run indents the item, if applicable. Subsequent runs outdent it at meaningful levels in the list. When done, item is put back at its original position with its original bullet. Return t at each successful move." (when (org-at-item-p) - (let* ((org-adapt-indentation nil) - (struct (org-list-struct)) - (ind (org-list-get-ind (point-at-bol) struct)) - (bullet (org-trim (buffer-substring (point-at-bol) (point-at-eol))))) + (let* ((struct (org-list-struct)) + (item (line-beginning-position)) + (ind (org-list-get-ind item struct))) ;; Accept empty items or if cycle has already started. (when (or (eq last-command 'org-cycle-item-indentation) - (and (save-excursion - (beginning-of-line) - (looking-at org-list-full-item-re)) - (>= (match-end 0) (save-excursion - (goto-char (org-list-get-item-end - (point-at-bol) struct)) - (skip-chars-backward " \r\t\n") - (point))))) + (and (org-match-line org-list-full-item-re) + (>= (match-end 0) + (save-excursion + (goto-char (org-list-get-item-end item struct)) + (skip-chars-backward " \t\n") + (point))))) (setq this-command 'org-cycle-item-indentation) - ;; When in the middle of the cycle, try to outdent first. If - ;; it fails, and point is still at initial position, indent. - ;; Else, re-create it at its original position. - (if (eq last-command 'org-cycle-item-indentation) + (let ((prevs (org-list-prevs-alist struct)) + (parents (org-list-parents-alist struct))) + (if (eq last-command 'org-cycle-item-indentation) + ;; When in the middle of the cycle, try to outdent. If + ;; it fails, move point back to its initial position and + ;; reset cycle. + (pcase-let ((`(,old-ind . ,old-bul) org-tab-ind-state) + (allow-outdent + (lambda (struct prevs parents) + ;; Non-nil if current item can be + ;; outdented. + (and (not (org-list-get-next-item item nil prevs)) + (not (org-list-has-child-p item struct)) + (org-list-get-parent item struct parents))))) + (cond + ((and (> ind old-ind) + (org-list-get-prev-item item nil prevs)) + (org-list-indent-item-generic 1 t struct)) + ((and (< ind old-ind) + (funcall allow-outdent struct prevs parents)) + (org-list-indent-item-generic -1 t struct)) + (t + (delete-region (line-beginning-position) (line-end-position)) + (indent-to-column old-ind) + (insert old-bul " ") + (let* ((struct (org-list-struct)) + (parents (org-list-parents-alist struct))) + (if (and (> ind old-ind) + ;; We were previously indenting item. It + ;; is no longer possible. Try to outdent + ;; from initial position. + (funcall allow-outdent + struct + (org-list-prevs-alist struct) + parents)) + (org-list-indent-item-generic -1 t struct) + (org-list-write-struct struct parents) + ;; Start cycle over. + (setq this-command 'identity) + t))))) + ;; If a cycle is starting, remember initial indentation + ;; and bullet, then try to indent. If it fails, try to + ;; outdent. + (setq org-tab-ind-state + (cons ind (org-trim (org-current-line-string)))) (cond - ((ignore-errors (org-list-indent-item-generic -1 t struct))) - ((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)) - (indent-to-column (car org-tab-ind-state)) - (insert (cdr org-tab-ind-state) " ") - ;; Break cycle - (setq this-command 'identity))) - ;; If a cycle is starting, remember indentation and bullet, - ;; then try to indent. If it fails, try to outdent. - (setq org-tab-ind-state (cons ind bullet)) - (cond - ((ignore-errors (org-list-indent-item-generic 1 t struct))) - ((ignore-errors (org-list-indent-item-generic -1 t struct))) - (t (user-error "Cannot move item")))) - t)))) + ((org-list-get-prev-item item nil prevs) + (org-list-indent-item-generic 1 t struct)) + ((and (not (org-list-get-next-item item nil prevs)) + (org-list-get-parent item struct parents)) + (org-list-indent-item-generic -1 t struct)) + (t + ;; This command failed. So will the following one. + ;; There's no point in starting the cycle. + (setq this-command 'identity) + (user-error "Cannot move item"))))))))) (defun org-sort-list (&optional with-case sorting-type getkey-func compare-func interactive?) @@ -2794,8 +2874,8 @@ if the current locale allows for it. 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 ?f ?F ?x ?X). Here is the -detailed meaning of each character: +be a character, among ?n ?N ?a ?A ?t ?T ?f ?F ?x or ?X. Here is +the detailed meaning of each character: n Numerically, by converting the beginning of the item to a number. a Alphabetically. Only the first line of item is checked. @@ -2958,7 +3038,7 @@ With a prefix argument ARG, change the region in a single item." (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)) + (setq beg (point-at-bol) end (copy-marker (point-at-eol)))) ;; Depending on the starting line, choose an action on the text ;; between BEG and END. @@ -3501,4 +3581,8 @@ overruling parameters for `org-list-to-generic'." (provide 'org-list) +;; Local variables: +;; generated-autoload-file: "org-loaddefs.el" +;; End: + ;;; org-list.el ends here diff --git a/lisp/org/org-macro.el b/lisp/org/org-macro.el index a1b987a8e26..5ddfae4e1f6 100644 --- a/lisp/org/org-macro.el +++ b/lisp/org/org-macro.el @@ -50,6 +50,7 @@ (require 'org-macs) (require 'org-compat) +(declare-function org-collect-keywords "org" (keywords &optional unique directory)) (declare-function org-element-at-point "org-element" ()) (declare-function org-element-context "org-element" (&optional element)) (declare-function org-element-copy "org-element" (datum)) @@ -88,49 +89,24 @@ directly, use instead: VALUE is the template of the macro. The new value override the previous one, unless VALUE is nil. TEMPLATES is the list of templates. Return the updated list." - (when value - (let ((old-definition (assoc name templates))) - (if old-definition - (setcdr old-definition value) - (push (cons name value) templates)))) + (let ((old-definition (assoc name templates))) + (cond ((and value old-definition) (setcdr old-definition value)) + (old-definition) + (t (push (cons name (or value "")) templates)))) templates) -(defun org-macro--collect-macros (&optional files templates) +(defun org-macro--collect-macros () "Collect macro definitions in current buffer and setup files. -Return an alist containing all macro templates found. - -FILES is a list of setup files names read so far, used to avoid -circular dependencies. TEMPLATES is the alist collected so far. -The two arguments are used in recursive calls." - (let ((case-fold-search t)) - (org-with-point-at 1 - (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 "MACRO" (org-element-property :key element)) - ;; Install macro in TEMPLATES. - (when (string-match "^\\(\\S-+\\)[ \t]*" val) - (let ((name (match-string 1 val)) - (value (substring val (match-end 0)))) - (setq templates - (org-macro--set-template name value templates)))) - ;; Enter setup file. - (let* ((uri (org-strip-quotes val)) - (uri-is-url (org-file-url-p uri)) - (uri (if uri-is-url - uri - (expand-file-name uri)))) - ;; Avoid circular dependencies. - (unless (member uri files) - (with-temp-buffer - (unless uri-is-url - (setq default-directory (file-name-directory uri))) - (org-mode) - (insert (org-file-contents uri 'noerror)) - (setq templates - (org-macro--collect-macros - (cons uri files) templates))))))))))) +Return an alist containing all macro templates found." + (let ((templates nil)) + (pcase (org-collect-keywords '("MACRO")) + (`(("MACRO" . ,values)) + (dolist (value values) + (when (string-match "^\\(\\S-+\\)[ \t]*" value) + (let ((name (match-string 1 value)) + (definition (substring value (match-end 0)))) + (setq templates + (org-macro--set-template name definition templates))))))) (let ((macros `(("author" . ,(org-macro--find-keyword-value "AUTHOR")) ("email" . ,(org-macro--find-keyword-value "EMAIL")) ("title" . ,(org-macro--find-keyword-value "TITLE" t)) @@ -417,6 +393,6 @@ Any other non-empty string resets the counter to 1." (t 1)) org-macro--counter-table))) - (provide 'org-macro) + ;;; org-macro.el ends here diff --git a/lisp/org/org-macs.el b/lisp/org/org-macs.el index 2a7ab66a339..f25efe07f33 100644 --- a/lisp/org/org-macs.el +++ b/lisp/org/org-macs.el @@ -34,6 +34,7 @@ (require 'cl-lib) (require 'format-spec) +(declare-function org-show-context "org" (&optional key)) (declare-function org-string-collate-lessp "org-compat" (s1 s2 &optional locale ignore-case)) (defvar org-ts-regexp0) @@ -122,7 +123,7 @@ means that the buffer should stay alive during the operation, because otherwise all these markers will point to nowhere." (declare (debug (form body)) (indent 1)) (org-with-gensyms (data invisible-types markers?) - `(let* ((,invisible-types '(org-hide-block org-hide-drawer outline)) + `(let* ((,invisible-types '(org-hide-block outline)) (,markers? ,use-markers) (,data (mapcar (lambda (o) @@ -416,6 +417,7 @@ is selected, only the bare key is returned." (let ((inhibit-quit t) (buffer (org-switch-to-buffer-other-window "*Org Select*")) (prompt (or prompt "Select: ")) + case-fold-search current) (unwind-protect (catch 'exit @@ -644,6 +646,25 @@ The number of levels is controlled by `org-inlinetask-min-level'." limit-level))) (format "\\*\\{1,%d\\} " nstars))))) +(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'." + (and (not (bobp)) + (save-excursion + (beginning-of-line n) + (looking-at-p "[ \t]*$")))) + +(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)) + +(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)) + ;;; Motion @@ -695,7 +716,9 @@ SPEC is the invisibility spec, as a symbol." (let ((o (make-overlay from to nil 'front-advance))) (overlay-put o 'evaporate t) (overlay-put o 'invisible spec) - (overlay-put o 'isearch-open-invisible #'delete-overlay)))) + (overlay-put o + 'isearch-open-invisible + (lambda (&rest _) (org-show-context 'isearch)))))) @@ -920,7 +943,8 @@ if necessary." (if (<= (length s) maxlength) s (let* ((n (max (- maxlength 4) 1)) - (re (concat "\\`\\(.\\{1," (int-to-string n) "\\}[^ ]\\)\\([ ]\\|\\'\\)"))) + (re (concat "\\`\\(.\\{1," (number-to-string n) + "\\}[^ ]\\)\\([ ]\\|\\'\\)"))) (if (string-match re s) (concat (match-string 1 s) "...") (concat (substring s 0 (max (- maxlength 3) 0)) "..."))))) @@ -1065,10 +1089,16 @@ the value in cdr." (get-text-property (or (next-single-property-change 0 prop s) 0) prop s))) -(defun org-invisible-p (&optional pos) +(defun org-invisible-p (&optional pos folding-only) "Non-nil if the character after POS is invisible. -If POS is nil, use `point' instead." - (get-char-property (or pos (point)) 'invisible)) +If POS is nil, use `point' instead. When optional argument +FOLDING-ONLY is non-nil, only consider invisible parts due to +folding of a headline, a block or a drawer, i.e., not because of +fontification." + (let ((value (get-char-property (or pos (point)) 'invisible))) + (cond ((not value) nil) + (folding-only (memq value '(org-hide-block outline))) + (t value)))) (defun org-truely-invisible-p () "Check if point is at a character currently not visible. @@ -1086,6 +1116,18 @@ move it back by one char before doing this check." (backward-char 1)) (org-invisible-p))) +(defun org-find-visible () + "Return closest visible buffer position, or `point-max'" + (if (org-invisible-p) + (next-single-char-property-change (point) 'invisible) + (point))) + +(defun org-find-invisible () + "Return closest invisible buffer position, or `point-max'" + (if (org-invisible-p) + (point) + (next-single-char-property-change (point) 'invisible))) + ;;; Time @@ -1182,8 +1224,41 @@ Return 0. if S is not recognized as a valid value." ((string-match org-ts-regexp0 s) (org-2ft s)) (t 0.))))) - +(defun org-scroll (key &optional additional-keys) + "Receive KEY and scroll the current window accordingly. +When ADDITIONAL-KEYS is not nil, also include SPC and DEL in the +allowed keys for scrolling, as expected in the export dispatch +window." + (let ((scrlup (if additional-keys '(?\s 22) 22)) + (scrldn (if additional-keys `(?\d 134217846) 134217846))) + (eval + `(cl-case ,key + ;; C-n + (14 (if (not (pos-visible-in-window-p (point-max))) + (ignore-errors (scroll-up 1)) + (message "End of buffer") + (sit-for 1))) + ;; C-p + (16 (if (not (pos-visible-in-window-p (point-min))) + (ignore-errors (scroll-down 1)) + (message "Beginning of buffer") + (sit-for 1))) + ;; SPC or + (,scrlup + (if (not (pos-visible-in-window-p (point-max))) + (scroll-up nil) + (message "End of buffer") + (sit-for 1))) + ;; DEL + (,scrldn (if (not (pos-visible-in-window-p (point-min))) + (scroll-down nil) + (message "Beginning of buffer") + (sit-for 1))))))) (provide 'org-macs) +;; Local variables: +;; generated-autoload-file: "org-loaddefs.el" +;; End: + ;;; org-macs.el ends here diff --git a/lisp/org/org-mobile.el b/lisp/org/org-mobile.el index 8749e496c25..6df567d6377 100644 --- a/lisp/org/org-mobile.el +++ b/lisp/org/org-mobile.el @@ -258,6 +258,17 @@ the old and new values for the entry.") (defvar org-mobile-files-alist nil) (defvar org-mobile-checksum-files nil) +;; Add org mobile commands to the main org menu +(easy-menu-add-item + org-org-menu + nil + '("MobileOrg" + ["Push Files and Views" org-mobile-push t] + ["Get Captured and Flagged" org-mobile-pull t] + ["Find FLAGGED Tasks" (org-agenda nil "?") :active t :keys "\\[org-agenda] ?"] + "--" + ["Setup" (customize-group 'org-mobile) t])) + (defun org-mobile-prepare-file-lists () (setq org-mobile-files-alist (org-mobile-files-alist)) (setq org-mobile-checksum-files nil)) diff --git a/lisp/org/org-mouse.el b/lisp/org/org-mouse.el index 02798874d24..d8877630241 100644 --- a/lisp/org/org-mouse.el +++ b/lisp/org/org-mouse.el @@ -386,7 +386,7 @@ DEFAULT is returned if no priority is given in the headline." (save-excursion (if (org-mouse-re-search-line org-mouse-priority-regexp) (match-string 1) - (when default (char-to-string org-default-priority))))) + (when default (char-to-string org-priority-default))))) (defun org-mouse-delete-timestamp () "Deletes the current timestamp as well as the preceding keyword. @@ -407,7 +407,7 @@ SCHEDULED: or DEADLINE: or ANYTHINGLIKETHIS:" (> (match-end 0) point)))))) (defun org-mouse-priority-list () - (cl-loop for priority from ?A to org-lowest-priority + (cl-loop for priority from ?A to org-priority-lowest collect (char-to-string priority))) (defun org-mouse-todo-menu (state) @@ -495,7 +495,8 @@ SCHEDULED: or DEADLINE: or ANYTHINGLIKETHIS:" ["Check Deadlines" (if (functionp 'org-check-deadlines-and-todos) (org-check-deadlines-and-todos org-deadline-warning-days) - (org-check-deadlines org-deadline-warning-days)) t] + (org-check-deadlines org-deadline-warning-days)) + t] ["Check TODOs" org-show-todo-tree t] ("Check Tags" ,@(org-mouse-keyword-menu @@ -741,7 +742,8 @@ This means, between the beginning of line and the point." (?$ "($) Formula Parameters") (?# "(#) Recalculation: Auto") (?* "(*) Recalculation: Manual") - (?' "(') Recalculation: None"))) t)))) + (?' "(') Recalculation: None"))) + t)))) ((assq :table contextlist) (popup-menu '(nil diff --git a/lisp/org/org-num.el b/lisp/org/org-num.el index 5b8e1dbb6d1..167db18ed2d 100644 --- a/lisp/org/org-num.el +++ b/lisp/org/org-num.el @@ -254,6 +254,7 @@ otherwise." org-footnote-section (equal title org-footnote-section)) (and org-num-skip-commented + title (let ((case-fold-search nil)) (string-match org-num--comment-re title)) t) @@ -466,6 +467,10 @@ NUMBERING is a list of numbers." (remove-hook 'after-change-functions #'org-num--verify t) (remove-hook 'change-major-mode-hook #'org-num--clear t)))) - (provide 'org-num) + +;; Local variables: +;; generated-autoload-file: "org-loaddefs.el" +;; End: + ;;; org-num.el ends here diff --git a/lisp/org/org-pcomplete.el b/lisp/org/org-pcomplete.el index b0ebbc4c241..4b2da9d6948 100644 --- a/lisp/org/org-pcomplete.el +++ b/lisp/org/org-pcomplete.el @@ -32,6 +32,8 @@ (require 'pcomplete) (declare-function org-at-heading-p "org" (&optional ignored)) +(declare-function org-babel-combine-header-arg-lists "ob-core" (original &rest others)) +(declare-function org-babel-get-src-block-info "ob-core" (&optional light datum)) (declare-function org-before-first-heading-p "org" ()) (declare-function org-buffer-property-keys "org" (&optional specials defaults columns)) (declare-function org-element-at-point "org-element" ()) @@ -47,8 +49,9 @@ (declare-function org-link-heading-search-string "ol" (&optional string)) (declare-function org-tag-alist-to-string "org" (alist &optional skip-key)) +(defvar org-babel-common-header-args-w-values) (defvar org-current-tag-alist) -(defvar org-default-priority) +(defvar org-priority-default) (defvar org-drawer-regexp) (defvar org-element-affiliated-keywords) (defvar org-entities) @@ -56,10 +59,10 @@ (defvar org-export-exclude-tags) (defvar org-export-select-tags) (defvar org-file-tags) -(defvar org-highest-priority) +(defvar org-priority-highest) (defvar org-link-abbrev-alist) (defvar org-link-abbrev-alist-local) -(defvar org-lowest-priority) +(defvar org-priority-lowest) (defvar org-options-keywords) (defvar org-outline-regexp) (defvar org-property-re) @@ -252,9 +255,9 @@ When completing for #+STARTUP, for example, this function returns (defun pcomplete/org-mode/file-option/priorities () "Complete arguments for the #+PRIORITIES file option." (pcomplete-here (list (format "%c %c %c" - org-highest-priority - org-lowest-priority - org-default-priority)))) + org-priority-highest + org-priority-lowest + org-priority-default)))) (defun pcomplete/org-mode/file-option/select_tags () "Complete arguments for the #+SELECT_TAGS file option." @@ -352,8 +355,9 @@ This needs more work, to handle headings with lots of spaces in them." (goto-char (point-min)) (let (tbl) (while (re-search-forward org-outline-regexp nil t) - (push (org-link-heading-search-string (org-get-heading t t t t)) - tbl)) + ;; Remove the leading asterisk from + ;; `org-link-heading-search-string' result. + (push (substring (org-link-heading-search-string) 1) tbl)) (pcomplete-uniquify-list tbl))) ;; When completing a bracketed link, i.e., "[[*", argument ;; starts at the star, so remove this character. @@ -417,11 +421,17 @@ switches." (symbol-plist 'org-babel-load-languages) 'custom-type))))))) - (while (pcomplete-here - '("-n" "-r" "-l" - ":cache" ":colnames" ":comments" ":dir" ":eval" ":exports" - ":file" ":hlines" ":no-expand" ":noweb" ":results" ":rownames" - ":session" ":shebang" ":tangle" ":tangle-mode" ":var")))) + (let* ((info (org-babel-get-src-block-info 'light)) + (lang (car info)) + (lang-headers (intern (concat "org-babel-header-args:" lang))) + (headers (org-babel-combine-header-arg-lists + org-babel-common-header-args-w-values + (and (boundp lang-headers) (eval lang-headers t))))) + (while (pcomplete-here + (append (mapcar + (lambda (arg) (format ":%s" (symbol-name (car arg)))) + headers) + '("-n" "-r" "-l")))))) (defun pcomplete/org-mode/block-option/clocktable () "Complete keywords in a clocktable line." diff --git a/lisp/org/org-plot.el b/lisp/org/org-plot.el index 8bf883921c9..57b280fe383 100644 --- a/lisp/org/org-plot.el +++ b/lisp/org/org-plot.el @@ -3,6 +3,7 @@ ;; Copyright (C) 2008-2020 Free Software Foundation, Inc. ;; ;; Author: Eric Schulte +;; Maintainer: TEC ;; Keywords: tables, plotting ;; Homepage: https://orgmode.org ;; @@ -144,7 +145,8 @@ and dependent variables." row-vals) (when (>= ind 0) ;; collect values of ind col (setf row-vals (mapcar (lambda (row) (setf counter (+ 1 counter)) - (cons counter (nth ind row))) table))) + (cons counter (nth ind row))) + table))) (when (or deps (>= ind 0)) ;; remove non-plotting columns (setf deps (delq ind deps)) (setf table (mapcar (lambda (row) @@ -288,14 +290,12 @@ line directly before or after the table." (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 (nth 0 table) 'hline) (nth 1 table) - (nth 0 table))))) + (table (org-table-collapse-header (org-table-to-lisp))) + (num-cols (length (car 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 + (plist-put params :labels (car 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)) @@ -308,26 +308,20 @@ line directly before or after the table." (`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 (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) - (plist-put params :timeind t) - ;; 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)) - (plist-put params :textind t))))) + ;; Check type of ind column (timestamp? text?) + (when (eq `2d (plist-get params :plot-type)) + (let* ((ind (1- (plist-get params :ind))) + (ind-column (mapcar (lambda (row) (nth ind row)) table))) + (cond ((< ind 0) nil) ; ind is implicit + ((cl-every (lambda (el) + (string-match org-ts-regexp3 el)) + ind-column) + (plist-put params :timeind t)) ; ind holds timestamps + ((or (string= (plist-get params :with) "hist") + (cl-notevery (lambda (el) + (string-match org-table-number-regexp el)) + ind-column)) + (plist-put params :textind t))))) ; ind holds text ;; Write script. (with-temp-buffer (if (plist-get params :script) ; user script diff --git a/lisp/org/org-protocol.el b/lisp/org/org-protocol.el index 0ff0e401d27..4bc7cee31fc 100644 --- a/lisp/org/org-protocol.el +++ b/lisp/org/org-protocol.el @@ -191,7 +191,7 @@ Example: :working-suffix \".org\" :base-url \"https://orgmode.org/worg/\" :working-directory \"/home/user/org/Worg/\") - (\"http://localhost/org-notes/\" + (\"localhost org-notes/\" :online-suffix \".html\" :working-suffix \".org\" :base-url \"http://localhost/org/\" @@ -202,12 +202,17 @@ Example: :working-directory \"~/site/content/post/\" :online-suffix \".html\" :working-suffix \".md\" - :rewrites ((\"\\(https://site.com/[0-9]+/[0-9]+/[0-9]+/\\)\" . \".md\"))))) - - - The last line tells `org-protocol-open-source' to open - /home/user/org/index.php, if the URL cannot be mapped to an existing - file, and ends with either \"org\" or \"org/\". + :rewrites ((\"\\(https://site.com/[0-9]+/[0-9]+/[0-9]+/\\)\" . \".md\"))) + (\"GNU emacs OpenGrok\" + :base-url \"https://opengrok.housegordon.com/source/xref/emacs/\" + :working-directory \"~/dev/gnu-emacs/\"))) + + The :rewrites line of \"localhost org-notes\" entry tells + `org-protocol-open-source' to open /home/user/org/index.php, + if the URL cannot be mapped to an existing file, and ends with + either \"org\" or \"org/\". The \"GNU emacs OpenGrok\" entry + does not include any suffix properties, allowing local source + file to be opened as found by OpenGrok. Consider using the interactive functions `org-protocol-create' and `org-protocol-create-for-org' to help you filling this variable with valid contents." @@ -278,7 +283,7 @@ This should be a single regexp string." :group 'org-protocol :version "24.4" :package-version '(Org . "8.0") - :type 'string) + :type 'regexp) ;;; Helper functions: @@ -545,11 +550,12 @@ The location for a browser's bookmark should look like this: ;; ending than strip-suffix here: (f1 (substring f 0 (string-match "\\([\\?#].*\\)?$" f))) (start-pos (+ (string-match wsearch f1) (length base-url))) - (end-pos (string-match - (regexp-quote strip-suffix) f1)) + (end-pos (if strip-suffix + (string-match (regexp-quote strip-suffix) f1) + (length f1))) ;; We have to compare redirects without suffix below: (f2 (concat wdir (substring f1 start-pos end-pos))) - (the-file (concat f2 add-suffix))) + (the-file (if add-suffix (concat f2 add-suffix) f2))) ;; Note: the-file may still contain `%C3' et al here because browsers ;; tend to encode `ä' in URLs to `%25C3' - `%25' being `%'. @@ -617,13 +623,13 @@ CLIENT is ignored." (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))) - (new-style (string= (match-string 1 fname) "?"))) + (new-style (string-match "/*?" (match-string 1 fname)))) (when (plist-get (cdr prolist) :kill-client) (message "Greedy org-protocol handler. Killing client.") (server-edit)) diff --git a/lisp/org/org-refile.el b/lisp/org/org-refile.el new file mode 100644 index 00000000000..2a3fad53e80 --- /dev/null +++ b/lisp/org/org-refile.el @@ -0,0 +1,742 @@ +;;; org-refile.el --- Refile Org Subtrees -*- lexical-binding: t; -*- + +;; Copyright (C) 2010-2020 Free Software Foundation, Inc. + +;; Author: Carsten Dominik +;; Keywords: outlines, hypermedia, calendar, wp +;; +;; This file is part of GNU Emacs. + +;; This program 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. + +;; This program 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 this program. If not, see . + +;;; Commentary: + +;; Org Refile allows you to refile subtrees to various locations. + +;;; Code: + +(require 'org) + +(declare-function org-inlinetask-remove-END-maybe "org-inlinetask" ()) + +(defgroup org-refile nil + "Options concerning refiling entries in Org mode." + :tag "Org Refile" + :group 'org) + +(defcustom org-log-refile nil + "Information to record when a task is refiled. + +Possible values are: + +nil Don't add anything +time Add a time stamp to the task +note Prompt for a note and add it with template `org-log-note-headings' + +This option can also be set with on a per-file-basis with + + #+STARTUP: nologrefile + #+STARTUP: logrefile + #+STARTUP: lognoterefile + +You can have local logging settings for a subtree by setting the LOGGING +property to one or more of these keywords. + +When bulk-refiling, e.g., from the agenda, the value `note' is +forbidden and will temporarily be changed to `time'." + :group 'org-refile + :group 'org-progress + :version "24.1" + :type '(choice + (const :tag "No logging" nil) + (const :tag "Record timestamp" time) + (const :tag "Record timestamp with note." note))) + +(defcustom org-refile-targets nil + "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 + a file name or a list of file names. If you use `org-agenda-files' for + that, all agenda files will be scanned for targets. Nil means consider + headings in the current buffer. +- A specification of how to find candidate refile targets. This may be + any of: + - a cons cell (:tag . \"TAG\") to identify refile targets by a tag. + This tag has to be present in all target headlines, inheritance will + not be considered. + - a cons cell (:todo . \"KEYWORD\") to identify refile targets by + todo keyword. + - a cons cell (:regexp . \"REGEXP\") with a regular expression matching + headlines that are refiling targets. + - a cons cell (:level . N). Any headline of level N is considered a target. + Note that, when `org-odd-levels-only' is set, level corresponds to + order in hierarchy, not to the number of stars. + - a cons cell (:maxlevel . N). Any headline with level <= N is a target. + Note that, when `org-odd-levels-only' is set, level corresponds to + order in hierarchy, not to the number of stars. + +Each element of this list generates a set of possible targets. +The union of these sets is presented (with completion) to +the user by `org-refile'. + +You can set the variable `org-refile-target-verify-function' to a function +to verify each headline found by the simple criteria above. + +When this variable is nil, all top-level headlines in the current buffer +are used, equivalent to the value `((nil . (:level . 1))'." + :group 'org-refile + :type '(repeat + (cons + (choice :value org-agenda-files + (const :tag "All agenda files" org-agenda-files) + (const :tag "Current buffer" nil) + (function) (variable) (file)) + (choice :tag "Identify target headline by" + (cons :tag "Specific tag" (const :value :tag) (string)) + (cons :tag "TODO keyword" (const :value :todo) (string)) + (cons :tag "Regular expression" (const :value :regexp) (regexp)) + (cons :tag "Level number" (const :value :level) (integer)) + (cons :tag "Max Level number" (const :value :maxlevel) (integer)))))) + +(defcustom org-refile-target-verify-function nil + "Function to verify if the headline at point should be a refile target. +The function will be called without arguments, with point at the +beginning of the headline. It should return t and leave point +where it is if the headline is a valid target for refiling. + +If the target should not be selected, the function must return nil. +In addition to this, it may move point to a place from where the search +should be continued. For example, the function may decide that the entire +subtree of the current entry should be excluded and move point to the end +of the subtree." + :group 'org-refile + :type '(choice + (const nil) + (function))) + +(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 \\[org-refile]' or, +if you find that easier, \ +`\\[universal-argument] \\[universal-argument] \\[universal-argument] \ +\\[org-refile]'." + :group 'org-refile + :version "24.1" + :type 'boolean) + +(defcustom org-refile-use-outline-path nil + "Non-nil means provide refile targets as paths. +So a level 3 headline will be available as level1/level2/level3. + +When the value is `file', also include the file name (without directory) +into the path. In this case, you can also stop the completion after +the file name, to get entries inserted as top level in the file. + +When `full-file-path', include the full file path. + +When `buffer-name', use the buffer name." + :group 'org-refile + :type '(choice + (const :tag "Not" nil) + (const :tag "Yes" t) + (const :tag "Start with file name" file) + (const :tag "Start with full file path" full-file-path) + (const :tag "Start with buffer name" buffer-name))) + +(defcustom org-outline-path-complete-in-steps t + "Non-nil means complete the outline path in hierarchical steps. +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) + +(defcustom org-refile-allow-creating-parent-nodes nil + "Non-nil means allow the creation of new nodes as refile targets. +New nodes are then created by adding \"/new node name\" to the completion +of an existing node. When the value of this variable is `confirm', +new node creation must be confirmed by the user (recommended). +When nil, the completion must match an existing entry. + +Note that, if the new heading is not seen by the criteria +listed in `org-refile-targets', multiple instances of the same +heading would be created by trying again to file under the new +heading." + :group 'org-refile + :type '(choice + (const :tag "Never" nil) + (const :tag "Always" t) + (const :tag "Prompt for confirmation" confirm))) + +(defcustom org-refile-active-region-within-subtree nil + "Non-nil means also refile active region within a subtree. + +By default `org-refile' doesn't allow refiling regions if they +don't contain a set of subtrees, but it might be convenient to +do so sometimes: in that case, the first line of the region is +converted to a headline before refiling." + :group 'org-refile + :version "24.1" + :type 'boolean) + +(defvar org-refile-target-table nil + "The list of refile targets, created by `org-refile'.") + +(defvar org-refile-cache nil + "Cache for refile targets.") + +(defvar org-refile-markers nil + "All the markers used for caching refile locations.") + +;; Add org refile commands to the main org menu +(mapc (lambda (i) (easy-menu-add-item + org-org-menu + '("Edit Structure") i)) + '(["Refile Subtree" org-refile (org-in-subtree-not-table-p)] + ["Refile and copy Subtree" org-copy (org-in-subtree-not-table-p)])) + +(defun org-refile-marker (pos) + "Get a new refile marker, but only if caching is in use." + (if (not org-refile-use-cache) + pos + (let ((m (make-marker))) + (move-marker m pos) + (push m org-refile-markers) + m))) + +(defun org-refile-cache-clear () + "Clear the refile cache and disable all the 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")) + +(defun org-refile-cache-check-set (set) + "Check if all the markers in the cache still have live buffers." + (let (marker) + (catch 'exit + (while (and set (setq marker (nth 3 (pop set)))) + ;; If `org-refile-use-outline-path' is 'file, marker may be nil + (when (and marker (null (marker-buffer marker))) + (message "Please regenerate the refile cache with `C-0 C-c C-w'") + (sit-for 3) + (throw 'exit nil))) + t))) + +(defun org-refile-cache-put (set &rest identifiers) + "Push the refile targets SET into the cache, under IDENTIFIERS." + (let* ((key (sha1 (prin1-to-string identifiers))) + (entry (assoc key org-refile-cache))) + (if entry + (setcdr entry set) + (push (cons key set) org-refile-cache)))) + +(defun org-refile-cache-get (&rest identifiers) + "Retrieve the cached value for refile targets given by IDENTIFIERS." + (cond + ((not org-refile-cache) nil) + ((not org-refile-use-cache) (org-refile-cache-clear) nil) + (t + (let ((set (cdr (assoc (sha1 (prin1-to-string identifiers)) + org-refile-cache)))) + (and set (org-refile-cache-check-set set) set))))) + +(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 files desc descre) + (message "Getting targets...") + (with-current-buffer (or default-buffer (current-buffer)) + (dolist (entry entries) + (setq files (car entry) desc (cdr entry)) + (cond + ((null files) (setq files (list (current-buffer)))) + ((eq files 'org-agenda-files) + (setq files (org-agenda-files 'unrestricted))) + ((and (symbolp files) (fboundp files)) + (setq files (funcall files))) + ((and (symbolp files) (boundp files)) + (setq files (symbol-value files)))) + (when (stringp files) (setq files (list files))) + (cond + ((eq (car desc) :tag) + (setq descre (concat "^\\*+[ \t]+.*?:" (regexp-quote (cdr desc)) ":"))) + ((eq (car desc) :todo) + (setq descre (concat "^\\*+[ \t]+" (regexp-quote (cdr desc)) "[ \t]"))) + ((eq (car desc) :regexp) + (setq descre (cdr desc))) + ((eq (car desc) :level) + (setq descre (concat "^\\*\\{" (number-to-string + (if org-odd-levels-only + (1- (* 2 (cdr desc))) + (cdr desc))) + "\\}[ \t]"))) + ((eq (car desc) :maxlevel) + (setq descre (concat "^\\*\\{1," (number-to-string + (if org-odd-levels-only + (1- (* 2 (cdr desc))) + (cdr desc))) + "\\}[ \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)) + (or + (setq tgs (org-refile-cache-get (buffer-file-name) descre)) + (progn + (when (bufferp f) + (setq f (buffer-file-name (buffer-base-buffer f)))) + (setq f (and f (expand-file-name f))) + (when (eq org-refile-use-outline-path 'file) + (push (list (file-name-nondirectory f) f nil nil) tgs)) + (when (eq org-refile-use-outline-path 'buffer-name) + (push (list (buffer-name (buffer-base-buffer)) f nil nil) tgs)) + (when (eq org-refile-use-outline-path 'full-file-path) + (push (list (file-truename (buffer-file-name (buffer-base-buffer))) 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 + #'identity + (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)))) + (`buffer-name + (list (buffer-name + (buffer-base-buffer)))) + (_ nil)) + (mapcar (lambda (s) (replace-regexp-in-string + "/" "\\/" s nil t)) + (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") + (delete-dups (nreverse targets)))) + +(defvar org-refile-history nil + "History for refiling operations.") + +(defvar org-after-refile-insert-hook nil + "Hook run after `org-refile' has inserted its stuff at the new location. +Note that this is still *before* the stuff will be removed from +the *old* location.") + +(defvar org-refile-keep nil + "Non-nil means `org-refile' will copy instead of refile.") + +(define-obsolete-function-alias 'org-copy 'org-refile-copy) + +;;;###autoload +(defun org-refile-copy () + "Like `org-refile', but preserve the refiled subtree." + (interactive) + (let ((org-refile-keep t)) + (org-refile nil nil nil "Copy"))) + +(defvar org-capture-last-stored-marker) + +;;;###autoload +(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. + +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 a `\\[universal-argument]' ARG, the command will only visit the target \ +location +and not actually move anything. + +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. + +With a numeric prefix argument of `3', emulate `org-refile-keep' +being set to t and copy to the target location, don't move it. +Beware that keeping refiled entries may result in duplicated ID +properties. + +RFLOC can be a refile location obtained in a different way. It +should be a list with the following 4 elements: + +1. Name - an identifier for the refile location, typically the +headline text +2. File - the file the refile location is in +3. nil - used for generating refile location candidates, not +needed when passing RFLOC +4. Position - the position in the specified file of the +headline to refile under + +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'. + +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 arg '(0 (64))) + (org-refile-cache-clear) + (let* ((actionmsg (cond (msg msg) + ((equal arg 3) "Refile (and keep)") + (t "Refile"))) + (regionp (org-region-active-p)) + (region-start (and regionp (region-beginning))) + (region-end (and regionp (region-end))) + (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) + (beginning-of-line) + (setq region-start (point)) + (unless (or (org-kill-is-subtree-p + (buffer-substring region-start region-end)) + (prog1 org-refile-active-region-within-subtree + (let ((s (point-at-eol))) + (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 arg '(16)) + (org-refile-goto-last-stored) + (when (or + (and (equal arg 2) + org-clock-hd-marker (marker-buffer org-clock-hd-marker) + (prog1 + (setq it (list (or org-clock-heading "running clock") + (buffer-file-name + (marker-buffer org-clock-hd-marker)) + "" + (marker-position org-clock-hd-marker))) + (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-link-bracket-re + "\\2" + (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) + pos (nth 3 it)) + (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 arg (not (equal arg 3))) + (progn + (pop-to-buffer-same-window nbuf) + (goto-char (cond (pos) + ((org-notes-order-reversed-p) (point-min)) + (t (point-max)))) + (org-show-context 'org-goto)) + (if regionp + (progn + (org-kill-new (buffer-substring region-start region-end)) + (org-save-markers-in-region region-start region-end)) + (org-copy-subtree 1 nil t)) + (with-current-buffer (setq nbuf (or (find-buffer-visiting file) + (find-file-noselect file))) + (setq reversed (org-notes-order-reversed-p)) + (org-with-wide-buffer + (if pos + (progn + (goto-char pos) + (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) + ;; Record information, according to `org-log-refile'. + ;; Do not prompt for a note when refiling multiple + ;; headlines, however. Simply add a time stamp. + (cond + ((not org-log-refile)) + (regionp + (org-map-region + (lambda () (org-add-log-setup 'refile nil nil 'time)) + (point) + (+ (point) (- region-end region-start)))) + (t + (org-add-log-setup 'refile nil nil org-log-refile))) + (and org-auto-align-tags + (let ((org-loop-over-headlines-in-active-region nil)) + (org-align-tags))) + (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))) + (org-preserve-local-variables + (delete-region + (and (org-back-to-heading t) (point)) + (min (1+ (buffer-size)) (org-end-of-subtree t t) (point)))))) + (when (featurep 'org-inlinetask) + (org-inlinetask-remove-END-maybe)) + (setq org-markers-to-move nil) + (message "%s to \"%s\" in file %s: done" actionmsg + (car it) file))))))) + +(defun org-refile-goto-last-stored () + "Go to the location where the last refile was stored." + (interactive) + (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) + "When user refile to REFLOC, find the associated target in TBL. +Also check `org-refile-target-table'." + (car (delq + nil + (mapcar + (lambda (r) (or (assoc r tbl) + (assoc r 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) + "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." + (let ((org-refile-targets org-refile-targets) + (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)) + (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 + #'completing-read)) + (extra (if org-refile-use-outline-path "/" "")) + (cbnex (concat (buffer-name) extra)) + (filename (and cfn (expand-file-name cfn))) + (tbl (mapcar + (lambda (x) + (if (and (not (member org-refile-use-outline-path + '(file full-file-path))) + (not (equal filename (nth 1 x)))) + (cons (concat (car x) extra " (" + (file-name-nondirectory (nth 1 x)) ")") + (cdr x)) + (cons (concat (car x) extra) (cdr x)))) + org-refile-target-table)) + (completion-ignore-case t) + cdef + (prompt (concat prompt + (or (and (car org-refile-history) + (concat " (default " (car org-refile-history) ")")) + (and (assoc cbnex tbl) (setq cdef cbnex) + (concat " (default " cbnex ")"))) ": ")) + pa answ parent-target child parent old-hist) + (setq old-hist org-refile-history) + (setq answ (funcall cfunc prompt tbl nil (not new-nodes) + nil 'org-refile-history + (or cdef (concat (car org-refile-history) extra)))) + (if (setq pa (org-refile--get-location answ tbl)) + (let* ((last-refile-loc (car org-refile-history)) + (last-refile-loc-path (concat last-refile-loc extra))) + (org-refile-check-position pa) + (when (or (not org-refile-history) + (not (eq old-hist org-refile-history)) + (not (equal (car pa) last-refile-loc-path))) + (setq org-refile-history + (cons (car pa) (if (assoc last-refile-loc tbl) + org-refile-history + (cdr org-refile-history)))) + (when (or (equal last-refile-loc-path (nth 1 org-refile-history)) + (equal last-refile-loc (nth 1 org-refile-history))) + (pop org-refile-history))) + pa) + (if (string-match "\\`\\(.*\\)/\\([^/]+\\)\\'" answ) + (progn + (setq parent (match-string 1 answ) + child (match-string 2 answ)) + (setq parent-target (org-refile--get-location parent tbl)) + (when (and parent-target + (or (eq new-nodes t) + (and (eq new-nodes 'confirm) + (y-or-n-p (format "Create new node \"%s\"? " + child))))) + (org-refile-new-child parent-target child))) + (user-error "Invalid target location"))))) + +(defun org-refile-check-position (refile-pointer) + "Check if the refile pointer matches the headline to which it points." + (let* ((file (nth 1 refile-pointer)) + (re (nth 2 refile-pointer)) + (pos (nth 3 refile-pointer)) + buffer) + (if (and (not (markerp pos)) (not file)) + (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 + (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." + (unless parent-target + (error "Cannot find parent for new node")) + (let ((file (nth 1 parent-target)) + (pos (nth 3 parent-target)) + level) + (with-current-buffer (or (find-buffer-visiting file) + (find-file-noselect file)) + (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)) + (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))) + +(provide 'org-refile) + +;; Local variables: +;; generated-autoload-file: "org-loaddefs.el" +;; End: + +;;; org-refile.el ends here diff --git a/lisp/org/org-src.el b/lisp/org/org-src.el index 7876deaba19..28733d0115b 100644 --- a/lisp/org/org-src.el +++ b/lisp/org/org-src.el @@ -148,6 +148,9 @@ the existing edit buffer." "How the source code edit buffer should be displayed. Possible values for this option are: +plain Show edit buffer using `display-buffer'. Users can + further control the display behavior by modifying + `display-buffer-alist' and its relatives. current-window Show edit buffer in the current window, keeping all other windows. split-window-below Show edit buffer below the current window, keeping all @@ -156,10 +159,12 @@ split-window-right Show edit buffer to the right of the current window, keeping all other windows. other-window Use `switch-to-buffer-other-window' to display edit buffer. reorganize-frame Show only two windows on the current frame, the current - window and the edit buffer. When exiting the edit buffer, - return to one window. + window and the edit buffer. other-frame Use `switch-to-buffer-other-frame' to display edit buffer. - Also, when exiting the edit buffer, kill that frame." + Also, when exiting the edit buffer, kill that frame. + +Values that modify the window layout (reorganize-frame, split-window-below, +split-window-right) will restore the layout after exiting the edit buffer." :group 'org-edit-structure :type '(choice (const current-window) @@ -232,11 +237,11 @@ green, respectability. :version "26.1" :package-version '(Org . "9.0")) -(defcustom org-src-tab-acts-natively nil +(defcustom org-src-tab-acts-natively t "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" + :package-version '(Org . "9.4") :group 'org-babel) @@ -276,6 +281,9 @@ issued in the language major mode buffer." (defvar-local org-src--remote nil) (put 'org-src--remote 'permanent-local t) +(defvar-local org-src--saved-temp-window-config nil) +(put 'org-src--saved-temp-window-config 'permanent-local t) + (defvar-local org-src--source-type nil "Type of element being edited, as a symbol.") (put 'org-src--source-type 'permanent-local t) @@ -355,6 +363,12 @@ where BEG and END are buffer positions and CONTENTS is a string." (end (progn (goto-char (org-element-property :end datum)) (search-backward "}" (line-beginning-position) t)))) (list beg end (buffer-substring-no-properties beg end)))) + ((eq type 'latex-fragment) + (let ((beg (org-element-property :begin datum)) + (end (org-with-point-at (org-element-property :end datum) + (skip-chars-backward " \t") + (point)))) + (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))) @@ -469,6 +483,10 @@ 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." + (when (memq org-src-window-setup '(reorganize-frame + split-window-below + split-window-right)) + (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)) @@ -540,6 +558,10 @@ Leave point in edit buffer." (setq org-src-source-file-name source-file-name) ;; Start minor mode. (org-src-mode) + ;; Clear undo information so we cannot undo back to the + ;; initial empty buffer. + (buffer-disable-undo (current-buffer)) + (buffer-enable-undo) ;; Move mark and point in edit buffer to the corresponding ;; location. (if remote @@ -792,9 +814,14 @@ Raise an error when current buffer is not a source editing buffer." (defun org-src-switch-to-buffer (buffer context) (pcase org-src-window-setup + (`plain + (when (eq context 'exit) (quit-restore-window)) + (pop-to-buffer buffer)) (`current-window (pop-to-buffer-same-window buffer)) (`other-window - (switch-to-buffer-other-window buffer)) + (let ((cur-win (selected-window))) + (org-switch-to-buffer-other-window buffer) + (when (eq context 'exit) (quit-restore-window cur-win)))) (`split-window-below (if (eq context 'exit) (delete-window) @@ -912,7 +939,7 @@ A coderef format regexp can only match at the end of a line." ;; 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 ""))))) + (while (search-forward "\n" nil t) (replace-match " "))))) contents 'remote)) ;; Report success. @@ -942,6 +969,46 @@ Throw an error when not at such a table." (table-recognize) t)) +(defun org-edit-latex-fragment () + "Edit LaTeX fragment at point." + (interactive) + (let ((context (org-element-context))) + (unless (and (eq 'latex-fragment (org-element-type context)) + (org-src--on-datum-p context)) + (user-error "Not on a LaTeX fragment")) + (let* ((contents + (buffer-substring-no-properties + (org-element-property :begin context) + (- (org-element-property :end context) + (org-element-property :post-blank context)))) + (delim-length (if (string-match "\\`\\$[^$]" contents) 1 2))) + ;; Make the LaTeX deliminators read-only. + (add-text-properties 0 delim-length + (list 'read-only "Cannot edit LaTeX deliminator" + 'front-sticky t + 'rear-nonsticky t) + contents) + (let ((l (length contents))) + (add-text-properties (- l delim-length) l + (list 'read-only "Cannot edit LaTeX deliminator" + 'front-sticky nil + 'rear-nonsticky nil) + contents)) + (org-src--edit-element + context + (org-src--construct-edit-buffer-name (buffer-name) "LaTeX fragment") + (org-src-get-lang-mode "latex") + (lambda () + ;; Blank lines break things, replace with a single newline. + (while (re-search-forward "\n[ \t]*\n" nil t) (replace-match "\n")) + ;; If within a table a newline would disrupt the structure, + ;; so remove newlines. + (goto-char (point-min)) + (when (org-element-lineage context '(table-cell)) + (while (search-forward "\n" nil t) (replace-match " ")))) + contents)) + t)) + (defun org-edit-latex-environment () "Edit LaTeX environment at point. \\ @@ -1182,8 +1249,11 @@ Throw an error if there is no such buffer." (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))) - + (set-marker end nil) + (when org-src--saved-temp-window-config + (unwind-protect + (set-window-configuration org-src--saved-temp-window-config) + (setq org-src--saved-temp-window-config nil))))) (provide 'org-src) diff --git a/lisp/org/org-table.el b/lisp/org/org-table.el index 49765472558..8dd3f392d2d 100644 --- a/lisp/org/org-table.el +++ b/lisp/org/org-table.el @@ -40,6 +40,8 @@ (require 'org-keys) (declare-function calc-eval "calc" (str &optional separator &rest args)) +(declare-function face-remap-remove-relative "face-remap" (cookie)) +(declare-function face-remap-add-relative "face-remap" (face &rest specs)) (declare-function org-at-timestamp-p "org" (&optional extended)) (declare-function org-delete-backward-char "org" (N)) (declare-function org-element-at-point "org-element" ()) @@ -164,6 +166,12 @@ table, obtained by prompting the user." :tag "Org Table Settings" :group 'org-table) +(defcustom org-table-header-line-p nil + "Activate `org-table-header-line-mode' by default?" + :type 'boolean + :package-version '(Org . "9.4") + :group 'org-table) + (defcustom org-table-default-size "5x2" "The default size for newly created tables, Columns x Rows." :group 'org-table-settings @@ -198,7 +206,7 @@ Other options offered by the customize interface are more restrictive." "^\\([<>]?[-+^.0-9]*[0-9][-+^.0-9eEdDx()%]*\\|[<>]?[-+]?0[xX][[:xdigit:].]+\\|[<>]?[-+]?[0-9]+#[0-9a-zA-Z.]+\\|nan\\|[-+u]?inf\\)$") (const :tag "Very General Number-Like, including hex and Calc radix, allows comma as decimal mark" "^\\([<>]?[-+^.,0-9]*[0-9][-+^.0-9eEdDx()%]*\\|[<>]?[-+]?0[xX][[:xdigit:].]+\\|[<>]?[-+]?[0-9]+#[0-9a-zA-Z.]+\\|nan\\|[-+u]?inf\\)$") - (string :tag "Regexp:"))) + (regexp :tag "Regexp:"))) (defcustom org-table-number-fraction 0.5 "Fraction of numbers in a column required to make the column align right. @@ -442,6 +450,59 @@ prevents it from hanging Emacs." :package-version '(Org . "8.3")) +;;; Org table header minor mode +(defun org-table-row-get-visible-string (&optional pos) + "Get the visible string of a table row. +This may be useful when columns have been shrunk." + (save-excursion + (when pos (goto-char pos)) + (goto-char (line-beginning-position)) + (let ((end (line-end-position)) str) + (while (progn (forward-char 1) (< (point) end)) + (let ((ov (car (overlays-at (point))))) + (if (not ov) + (push (char-to-string (char-after)) str) + (push (overlay-get ov 'display) str) + (goto-char (1- (overlay-end ov)))))) + (format "|%s" (mapconcat #'identity (reverse str) ""))))) + +(defvar-local org-table-header-overlay nil) +(defun org-table-header-set-header () + "Display the header of the table at point." + (when (overlayp org-table-header-overlay) + (delete-overlay org-table-header-overlay)) + (let* ((ws (window-start)) + (beg (save-excursion + (goto-char (org-table-begin)) + (while (or (org-at-table-hline-p) + (looking-at-p ".*|\\s-+<[rcl]?\\([0-9]+\\)?>")) + (move-beginning-of-line 2)) + (point))) + (end (save-excursion (goto-char beg) (point-at-eol)))) + (if (pos-visible-in-window-p beg) + (when (overlayp org-table-header-overlay) + (delete-overlay org-table-header-overlay)) + (setq org-table-header-overlay + (make-overlay ws (+ ws (- end beg)))) + (org-overlay-display + org-table-header-overlay + (org-table-row-get-visible-string beg) + 'org-table-header)))) + +;;;###autoload +(define-minor-mode org-table-header-line-mode + "Display the first row of the table at point in the header line." + nil " TblHeader" nil + (unless (eq major-mode 'org-mode) + (user-error "Cannot turn org table header mode outside org-mode buffers")) + (if org-table-header-line-mode + (add-hook 'post-command-hook #'org-table-header-set-header nil t) + (when (overlayp org-table-header-overlay) + (delete-overlay org-table-header-overlay) + (setq org-table-header-overlay nil)) + (remove-hook 'post-command-hook #'org-table-header-set-header t))) + + ;;; Regexps Constants (defconst org-table-any-line-regexp "^[ \t]*\\(|\\|\\+-[-+]\\)" @@ -860,19 +921,22 @@ nil When nil, the command tries to be smart and figure out the 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 - - when each line contains a comma, assume CSV material - - else, assume one or more SPACE characters as separator. +- when each line contains a TAB, assume TAB-separated material; +- when each line contains a comma, assume CSV material; +- else, assume one or more SPACE characters as separator. When non-nil, 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 -(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." +- (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." (interactive "f\nP") + (when (and (called-interactively-p 'any) + (not (string-match-p (rx "." (or "txt" "tsv" "csv") eos) file))) + (user-error "Cannot import such file")) (unless (bolp) (insert "\n")) (let ((beg (point)) (pm (point-max))) @@ -1181,7 +1245,7 @@ value." (save-excursion (let* ((pos (point)) (col (org-table-current-column)) - (cname (car (rassoc (int-to-string col) org-table-column-names))) + (cname (car (rassoc (number-to-string col) org-table-column-names))) (name (car (rassoc (list (count-lines org-table-current-begin-pos (line-beginning-position)) col) @@ -1290,25 +1354,20 @@ However, when FORCE is non-nil, create new columns if necessary." (while (< (point) end) (unless (org-at-table-hline-p) (org-table-goto-column col t) - (unless (search-forward "|" (line-end-position) t 2) - ;; Add missing vertical bar at the end of the row. - (end-of-line) - (insert "|")) - (insert " |")) + (insert "|")) (forward-line))) - (org-table-goto-column (1+ col)) + (org-table-goto-column col) (org-table-align) ;; Shift appropriately stored shrunk column numbers, then hide the ;; columns again. - (org-table--shrink-columns (mapcar (lambda (c) (if (<= c col) c (1+ c))) + (org-table--shrink-columns (mapcar (lambda (c) (if (< c col) c (1+ c))) shrunk-columns) beg end) (set-marker end nil) ;; Fix TBLFM formulas, if desirable. (when (or (not org-table-fix-formulas-confirm) (funcall org-table-fix-formulas-confirm "Fix formulas? ")) - (org-table-fix-formulas "$" nil (1- col) 1) - (org-table-fix-formulas "$LR" nil (1- col) 1)))) + (org-table-fix-formulas "$" nil (1- col) 1)))) (defun org-table-find-dataline () "Find a data line in the current table, which is needed for column commands. @@ -1431,6 +1490,8 @@ Swap with anything in target cell." (interactive) (unless (org-at-table-p) (user-error "Not at a table")) (org-table-find-dataline) + (when (save-excursion (skip-chars-forward " \t") (eolp)) + (search-backward "|")) ;snap into last column (org-table-check-inside-data-field nil t) (let* ((col (org-table-current-column)) (beg (org-table-begin)) @@ -1446,7 +1507,6 @@ Swap with anything in target cell." (and (looking-at "|[^|\n]+|") (replace-match "|"))) (forward-line))) - (org-table-goto-column (max 1 (1- col))) (org-table-align) ;; Shift appropriately stored shrunk column numbers, then hide the ;; columns again. @@ -1458,9 +1518,7 @@ Swap with anything in target cell." (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)))) + "$" (list (cons (number-to-string col) "INVALID")) col -1 col)))) ;;;###autoload (defun org-table-move-column-right () @@ -1521,11 +1579,7 @@ Swap with anything in target cell." (funcall org-table-fix-formulas-confirm "Fix formulas? ")) (org-table-fix-formulas "$" (list (cons (number-to-string col) (number-to-string colpos)) - (cons (number-to-string colpos) (number-to-string col)))) - (org-table-fix-formulas - "$LR" (list - (cons (number-to-string col) (number-to-string colpos)) - (cons (number-to-string colpos) (number-to-string col)))))))) + (cons (number-to-string colpos) (number-to-string col)))))))) ;;;###autoload (defun org-table-move-row-down () @@ -1958,9 +2012,9 @@ toggle `org-table-follow-field-mode'." (coord (if (eq org-table-use-standard-references t) (concat (org-number-to-letters (org-table-current-column)) - (int-to-string (org-table-current-dline))) - (concat "@" (int-to-string (org-table-current-dline)) - "$" (int-to-string (org-table-current-column))))) + (number-to-string (org-table-current-dline))) + (concat "@" (number-to-string (org-table-current-dline)) + "$" (number-to-string (org-table-current-column))))) (field (org-table-get-field)) (cw (current-window-configuration)) p) @@ -2005,7 +2059,7 @@ the table and kill the editing buffer." text) (goto-char (point-min)) (while (re-search-forward "^#.*\n?" nil t) (replace-match "")) - (while (re-search-forward "\\([ \t]*\n[ \t]*\\)+" nil t) + (while (re-search-forward "[ \t]*\n[ \t\n]*" nil t) (replace-match " ")) (setq text (org-trim (buffer-string))) (set-window-configuration cw) @@ -2060,7 +2114,7 @@ When NAMED is non-nil, look for a named equation." (org-table-current-column))) (scol (cond ((not named) (format "$%d" (org-table-current-column))) - ((and name (not (string-match "\\`LR[0-9]+\\'" name))) name) + (name) (t ref))) (name (or name ref)) (org-table-may-need-update nil) @@ -2193,11 +2247,10 @@ For all numbers larger than LIMIT, shift them by DELTA." (save-excursion (goto-char (org-table-end)) (while (let ((case-fold-search t)) (looking-at "[ \t]*#\\+tblfm:")) - (let ((msg "The formulas in #+TBLFM have been updated") - (re (concat key "\\([0-9]+\\)")) + (let ((re (concat key "\\([0-9]+\\)")) (re2 (when remove - (if (or (equal key "$") (equal key "$LR")) + (if (equal key "$") (format "\\(@[0-9]+\\)?%s%d=.*?\\(::\\|$\\)" (regexp-quote key) remove) (format "@%d\\$[0-9]+=.*?\\(::\\|$\\)" remove)))) @@ -2215,11 +2268,10 @@ For all numbers larger than LIMIT, shift them by DELTA." (setq s (match-string 1) n (string-to-number s)) (cond ((setq a (assoc s replace)) - (replace-match (concat key (cdr a)) t t) - (message msg)) + (replace-match (concat key (cdr a)) t t)) ((and limit (> n limit)) - (replace-match (concat key (int-to-string (+ n delta))) t t) - (message msg)))))) + (replace-match (concat key (number-to-string (+ n delta))) t t))))) + (message "The formulas in #+TBLFM have been updated")) (forward-line)))) ;;;###autoload @@ -2547,7 +2599,8 @@ location of point." ev (if (numberp ev) (number-to-string ev) ev) ev (if duration (org-table-time-seconds-to-string (string-to-number ev) - duration-output-format) ev)) + duration-output-format) + ev)) ;; Use <...> time-stamps so that Calc can handle them. (setq form @@ -2578,27 +2631,29 @@ location of point." ev))) (when org-table-formula-debug - (with-output-to-temp-buffer "*Substitution History*" - (princ (format "Substitution history of formula + (let ((wcf (current-window-configuration))) + (with-output-to-temp-buffer "*Substitution History*" + (princ (format "Substitution history of formula Orig: %s $xyz-> %s @r$c-> %s $1-> %s\n" orig formula form0 form)) - (if (consp ev) - (princ (format " %s^\nError: %s" - (make-string (car ev) ?\-) (nth 1 ev))) - (princ (format "Result: %s\nFormat: %s\nFinal: %s" - ev (or fmt "NONE") - (if fmt (format fmt (string-to-number ev)) ev))))) - (setq bw (get-buffer-window "*Substitution History*")) - (org-fit-window-to-buffer bw) - (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 (consp ev) + (princ (format " %s^\nError: %s" + (make-string (car ev) ?\-) (nth 1 ev))) + (princ (format "Result: %s\nFormat: %s\nFinal: %s" + ev (or fmt "NONE") + (if fmt (format fmt (string-to-number ev)) ev))))) + (setq bw (get-buffer-window "*Substitution History*")) + (org-fit-window-to-buffer bw) + (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 "") + (set-window-configuration wcf)))) (when (consp ev) (setq fmt nil ev "#ERROR")) (org-table-justify-field-maybe (format org-table-formula-field-format @@ -3099,7 +3154,7 @@ function assumes the table is already analyzed (i.e., using (let ((lhs (car e)) (rhs (cdr e))) (cond - ((string-match-p "\\`@-?[-+0-9]+\\$-?[0-9]+\\'" lhs) + ((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) @@ -3287,7 +3342,6 @@ Parameters get priority." (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 @@ -3768,14 +3822,16 @@ FACE, when non-nil, for the highlight." (defun org-table-toggle-coordinate-overlays () "Toggle the display of Row/Column numbers in tables." (interactive) - (setq org-table-overlay-coordinates (not org-table-overlay-coordinates)) - (message "Tables Row/Column numbers display turned %s" - (if org-table-overlay-coordinates "on" "off")) - (when (and (org-at-table-p) org-table-overlay-coordinates) - (org-table-align)) - (unless org-table-overlay-coordinates - (mapc 'delete-overlay org-table-coordinate-overlays) - (setq org-table-coordinate-overlays nil))) + (if (not (org-at-table-p)) + (user-error "Not on a table") + (setq org-table-overlay-coordinates (not org-table-overlay-coordinates)) + (when (and (org-at-table-p) org-table-overlay-coordinates) + (org-table-align)) + (unless org-table-overlay-coordinates + (mapc 'delete-overlay org-table-coordinate-overlays) + (setq org-table-coordinate-overlays nil)) + (message "Tables Row/Column numbers display turned %s" + (if org-table-overlay-coordinates "on" "off")))) ;;;###autoload (defun org-table-toggle-formula-debugger () @@ -4239,7 +4295,8 @@ extension of the given file name, and finally on the variable (and (string-match-p fileext f) f)) formats))) org-table-export-default-format) - t t) t t))) + t t) + t t))) (setq format (org-completing-read "Format: " formats nil nil deffmt-readable)))) @@ -4247,9 +4304,7 @@ extension of the given file name, and finally on the variable (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))))) + (table (org-table-to-lisp))) (unless (fboundp transform) (user-error "No such transformation function %s" transform)) (let (buf) @@ -4293,78 +4348,79 @@ FIELD is a string. WIDTH is a number. ALIGN is either \"c\", (move-marker org-table-aligned-end-marker end) (goto-char beg) (org-table-with-shrunk-columns - (let* ((indent (progn (looking-at "[ \t]*") (match-string 0))) - ;; Table's rows as lists of fields. Rules are replaced - ;; by nil. Trailing spaces are removed. - (fields (mapcar - (lambda (l) - (and (not (string-match-p org-table-hline-regexp l)) - (org-split-string l "[ \t]*|[ \t]*"))) - (split-string (buffer-substring beg end) "\n" t))) - ;; Compute number of columns. If the table contains no - ;; field, create a default table and bail out. - (columns-number - (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"))) + (let* ((table (org-table-to-lisp)) + (rows (remq 'hline table)) (widths nil) - (alignments nil)) - ;; Compute alignment and width for each column. - (dotimes (i columns-number) - (let* ((max-width 1) - (fixed-align? nil) - (numbers 0) - (non-empty 0)) - (dolist (row fields) - (let ((cell (or (nth i row) ""))) - (setq max-width (max max-width (org-string-width cell))) - (cond (fixed-align? nil) - ((equal cell "") nil) - ((string-match "\\`<\\([lrc]\\)[0-9]*>\\'" cell) - (setq fixed-align? (match-string 1 cell))) - (t - (cl-incf non-empty) - (when (string-match-p org-table-number-regexp cell) - (cl-incf numbers)))))) - (push max-width widths) - (push (cond - (fixed-align?) - ((>= numbers (* org-table-number-fraction non-empty)) "r") - (t "l")) - alignments))) - (setq widths (nreverse widths)) - (setq alignments (nreverse alignments)) + (alignments nil) + (columns-number 1)) + (if (null rows) + ;; Table contains only horizontal rules. Compute the + ;; number of columns anyway, and choose an arbitrary width + ;; and alignment. + (let ((end (line-end-position))) + (save-excursion + (while (search-forward "+" end t) + (cl-incf columns-number))) + (setq widths (make-list columns-number 1)) + (setq alignments (make-list columns-number "l"))) + ;; Compute alignment and width for each column. + (setq columns-number (apply #'max (mapcar #'length rows))) + (dotimes (i columns-number) + (let ((max-width 1) + (fixed-align? nil) + (numbers 0) + (non-empty 0)) + (dolist (row rows) + (let ((cell (or (nth i row) ""))) + (setq max-width (max max-width (org-string-width cell))) + (cond (fixed-align? nil) + ((equal cell "") nil) + ((string-match "\\`<\\([lrc]\\)[0-9]*>\\'" cell) + (setq fixed-align? (match-string 1 cell))) + (t + (cl-incf non-empty) + (when (string-match-p org-table-number-regexp cell) + (cl-incf numbers)))))) + (push max-width widths) + (push (cond + (fixed-align?) + ((>= numbers (* org-table-number-fraction non-empty)) "r") + (t "l")) + alignments))) + (setq widths (nreverse widths)) + (setq alignments (nreverse alignments))) ;; Store alignment of this table, for later editing of single ;; fields. (setq org-table-last-alignment alignments) (setq org-table-last-column-widths widths) ;; Build new table rows. Only replace rows that actually ;; changed. - (dolist (row fields) - (let ((previous (buffer-substring (point) (line-end-position))) - (new - (format "%s|%s|" - indent - (if (null row) ;horizontal rule - (mapconcat (lambda (w) (make-string (+ 2 w) ?-)) - widths - "+") - (let ((cells ;add missing fields - (append row - (make-list (- columns-number - (length row)) - "")))) - (mapconcat #'identity - (cl-mapcar #'org-table--align-field - cells - widths - alignments) - "|")))))) - (if (equal new previous) - (forward-line) - (insert new "\n") - (delete-region (point) (line-beginning-position 2))))) + (let ((rule (and (memq 'hline table) + (mapconcat (lambda (w) (make-string (+ 2 w) ?-)) + widths + "+"))) + (indent (progn (looking-at "[ \t]*|") (match-string 0)))) + (dolist (row table) + (let ((previous (buffer-substring (point) (line-end-position))) + (new + (concat indent + (if (eq row 'hline) rule + (let* ((offset (- columns-number (length row))) + (fields (if (= 0 offset) row + ;; Add missing fields. + (append row + (make-list offset ""))))) + (mapconcat #'identity + (cl-mapcar #'org-table--align-field + fields + widths + alignments) + "|"))) + "|"))) + (if (equal new previous) + (forward-line) + (insert new "\n") + (delete-region (point) (line-beginning-position 2)))))) (set-marker end nil) (when org-table-overlay-coordinates (org-table-overlay-coordinates)) (setq org-table-may-need-update nil)))))) @@ -4406,7 +4462,7 @@ Optional argument NEW may specify text to replace the current field content." ((not new) (concat (org-table--align-field field width align) "|")) - ((<= (org-string-width new) width) + ((and width (<= (org-string-width new) width)) (concat (org-table--align-field new width align) "|")) (t @@ -4758,7 +4814,7 @@ This function sets up the following dynamically scoped variables: (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))))) + (push (cons name (number-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\\)\\>" @@ -4817,23 +4873,10 @@ This function sets up the following dynamically scoped variables: ;; 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)))))) + (setq org-table-current-ncol + (length (org-split-string + (buffer-substring (line-beginning-position) (line-end-position)) + "[ \t]*|[ \t]*")))))) (defun org-table--force-dataline () "Move point to the closest data line in a table. @@ -5039,66 +5082,66 @@ When LOCAL is non-nil, show references for the table at point." (put 'orgtbl-mode :menu-tag "Org Table Mode") (easy-menu-define orgtbl-mode-menu orgtbl-mode-map "OrgTbl menu" - '("OrgTbl" - ["Create or convert" org-table-create-or-convert-from-region - :active (not (org-at-table-p)) :keys "C-c |" ] - "--" - ["Align" org-ctrl-c-ctrl-c :active (org-at-table-p) :keys "C-c C-c"] - ["Next Field" org-cycle :active (org-at-table-p) :keys "TAB"] - ["Previous Field" org-shifttab :active (org-at-table-p) :keys "S-TAB"] - ["Next Row" org-return :active (org-at-table-p) :keys "RET"] - "--" - ["Blank Field" org-table-blank-field :active (org-at-table-p) :keys "C-c SPC"] - ["Edit Field" org-table-edit-field :active (org-at-table-p) :keys "C-c ` "] - ["Copy Field from Above" - org-table-copy-down :active (org-at-table-p) :keys "S-RET"] - "--" - ("Column" - ["Move Column Left" org-metaleft :active (org-at-table-p) :keys "M-"] - ["Move Column Right" org-metaright :active (org-at-table-p) :keys "M-"] - ["Delete Column" org-shiftmetaleft :active (org-at-table-p) :keys "M-S-"] - ["Insert Column" org-shiftmetaright :active (org-at-table-p) :keys "M-S-"]) - ("Row" - ["Move Row Up" org-metaup :active (org-at-table-p) :keys "M-"] - ["Move Row Down" org-metadown :active (org-at-table-p) :keys "M-"] - ["Delete Row" org-shiftmetaup :active (org-at-table-p) :keys "M-S-"] - ["Insert Row" org-shiftmetadown :active (org-at-table-p) :keys "M-S-"] - ["Sort lines in region" org-table-sort-lines :active (org-at-table-p) :keys "C-c ^"] - "--" - ["Insert Hline" org-table-insert-hline :active (org-at-table-p) :keys "C-c -"]) - ("Rectangle" - ["Copy Rectangle" org-copy-special :active (org-at-table-p)] - ["Cut Rectangle" org-cut-special :active (org-at-table-p)] - ["Paste Rectangle" org-paste-special :active (org-at-table-p)] - ["Fill Rectangle" org-table-wrap-region :active (org-at-table-p)]) - "--" - ("Radio tables" - ["Insert table template" orgtbl-insert-radio-table - (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 ="] - ["Set Field Formula" (org-table-eval-formula '(4)) :active (org-at-table-p) :keys "C-u C-c ="] - ["Edit Formulas" org-table-edit-formulas :active (org-at-table-p) :keys "C-c '"] - ["Recalculate line" org-table-recalculate :active (org-at-table-p) :keys "C-c *"] - ["Recalculate all" (org-table-recalculate '(4)) :active (org-at-table-p) :keys "C-u C-c *"] - ["Iterate all" (org-table-recalculate '(16)) :active (org-at-table-p) :keys "C-u C-u C-c *"] - ["Toggle Recalculate Mark" org-table-rotate-recalc-marks :active (org-at-table-p) :keys "C-c #"] - ["Sum Column/Rectangle" org-table-sum - :active (or (org-at-table-p) (org-region-active-p)) :keys "C-c +"] - ["Which Column?" org-table-current-column :active (org-at-table-p) :keys "C-c ?"] - ["Debug Formulas" - org-table-toggle-formula-debugger :active (org-at-table-p) - :keys "C-c {" - :style toggle :selected org-table-formula-debug] - ["Show Col/Row Numbers" - 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"]))) + '("OrgTbl" + ["Create or convert" org-table-create-or-convert-from-region + :active (not (org-at-table-p)) :keys "C-c |" ] + "--" + ["Align" org-ctrl-c-ctrl-c :active (org-at-table-p) :keys "C-c C-c"] + ["Next Field" org-cycle :active (org-at-table-p) :keys "TAB"] + ["Previous Field" org-shifttab :active (org-at-table-p) :keys "S-TAB"] + ["Next Row" org-return :active (org-at-table-p) :keys "RET"] + "--" + ["Blank Field" org-table-blank-field :active (org-at-table-p) :keys "C-c SPC"] + ["Edit Field" org-table-edit-field :active (org-at-table-p) :keys "C-c ` "] + ["Copy Field from Above" + org-table-copy-down :active (org-at-table-p) :keys "S-RET"] + "--" + ("Column" + ["Move Column Left" org-metaleft :active (org-at-table-p) :keys "M-"] + ["Move Column Right" org-metaright :active (org-at-table-p) :keys "M-"] + ["Delete Column" org-shiftmetaleft :active (org-at-table-p) :keys "M-S-"] + ["Insert Column" org-shiftmetaright :active (org-at-table-p) :keys "M-S-"]) + ("Row" + ["Move Row Up" org-metaup :active (org-at-table-p) :keys "M-"] + ["Move Row Down" org-metadown :active (org-at-table-p) :keys "M-"] + ["Delete Row" org-shiftmetaup :active (org-at-table-p) :keys "M-S-"] + ["Insert Row" org-shiftmetadown :active (org-at-table-p) :keys "M-S-"] + ["Sort lines in region" org-table-sort-lines :active (org-at-table-p) :keys "C-c ^"] + "--" + ["Insert Hline" org-table-insert-hline :active (org-at-table-p) :keys "C-c -"]) + ("Rectangle" + ["Copy Rectangle" org-copy-special :active (org-at-table-p)] + ["Cut Rectangle" org-cut-special :active (org-at-table-p)] + ["Paste Rectangle" org-paste-special :active (org-at-table-p)] + ["Fill Rectangle" org-table-wrap-region :active (org-at-table-p)]) + "--" + ("Radio tables" + ["Insert table template" orgtbl-insert-radio-table + (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 ="] + ["Set Field Formula" (org-table-eval-formula '(4)) :active (org-at-table-p) :keys "C-u C-c ="] + ["Edit Formulas" org-table-edit-formulas :active (org-at-table-p) :keys "C-c '"] + ["Recalculate line" org-table-recalculate :active (org-at-table-p) :keys "C-c *"] + ["Recalculate all" (org-table-recalculate '(4)) :active (org-at-table-p) :keys "C-u C-c *"] + ["Iterate all" (org-table-recalculate '(16)) :active (org-at-table-p) :keys "C-u C-u C-c *"] + ["Toggle Recalculate Mark" org-table-rotate-recalc-marks :active (org-at-table-p) :keys "C-c #"] + ["Sum Column/Rectangle" org-table-sum + :active (or (org-at-table-p) (org-region-active-p)) :keys "C-c +"] + ["Which Column?" org-table-current-column :active (org-at-table-p) :keys "C-c ?"] + ["Debug Formulas" + org-table-toggle-formula-debugger :active (org-at-table-p) + :keys "C-c {" + :style toggle :selected org-table-formula-debug] + ["Show Col/Row Numbers" + 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"]))) ;;;###autoload (define-minor-mode orgtbl-mode @@ -5129,15 +5172,13 @@ When LOCAL is non-nil, show references for the table at point." orgtbl-line-start-regexp)) (when (fboundp 'font-lock-add-keywords) (font-lock-add-keywords nil orgtbl-extra-font-lock-keywords) - (org-restart-font-lock)) - (easy-menu-add orgtbl-mode-menu)) + (org-restart-font-lock))) (t (setq auto-fill-inhibit-regexp org-old-auto-fill-inhibit-regexp) (remove-hook 'before-change-functions 'org-before-change-function t) (when (fboundp 'font-lock-remove-keywords) (font-lock-remove-keywords nil orgtbl-extra-font-lock-keywords) (org-restart-font-lock)) - (easy-menu-remove orgtbl-mode-menu) (force-mode-line-update 'all)))) (defun orgtbl-make-binding (fun n &rest keys) @@ -5147,7 +5188,7 @@ command name. KEYS are keys that should be checked in for a command to execute outside of tables." (eval (list 'defun - (intern (concat "orgtbl-hijacker-command-" (int-to-string n))) + (intern (concat "orgtbl-hijacker-command-" (number-to-string n))) '(arg) (concat "In tables, run `" (symbol-name fun) "'.\n" "Outside of tables, run the binding of `" @@ -5401,17 +5442,56 @@ a radio table." ;;;###autoload (defun org-table-to-lisp (&optional txt) "Convert the table at point to a Lisp structure. + 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 (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]*")))) + (if txt + (with-temp-buffer + (insert txt) + (goto-char (point-min)) + (org-table-to-lisp)) + (save-excursion + (goto-char (org-table-begin)) + (let ((table nil)) + (while (re-search-forward "\\=[ \t]*|" nil t) + (let ((row nil)) + (if (looking-at "-") + (push 'hline table) + (while (not (progn (skip-chars-forward " \t") (eolp))) + (push (buffer-substring + (point) + (progn (re-search-forward "[ \t]*\\(|\\|$\\)") + (match-beginning 0))) + row)) + (push (nreverse row) table))) + (forward-line)) + (nreverse table))))) + +(defun org-table-collapse-header (table &optional separator max-header-lines) + "Collapse the lines before 'hline into a single header. + +The given TABLE is a list of lists as returned by `org-table-to-lisp'. +The leading lines before the first `hline' symbol are considered +forming the table header. This function collapses all leading header +lines into a single header line, followed by the `hline' symbol, and +the rest of the TABLE. Header cells are glued together with a space, +or the given SEPARATOR." + (while (eq (car table) 'hline) (pop table)) + (let* ((separator (or separator " ")) + (max-header-lines (or max-header-lines 4)) + (trailer table) + (header-lines (cl-loop for line in table + until (eq 'hline line) + collect (pop trailer)))) + (if (and trailer (<= (length header-lines) max-header-lines)) + (cons (apply #'cl-mapcar + (lambda (&rest x) + (org-trim + (mapconcat #'identity x separator))) + header-lines) + trailer) + table))) (defun orgtbl-send-table (&optional maybe) "Send a transformed version of table at point to the receiver position. @@ -5423,9 +5503,7 @@ for this table." ;; when non-interactive, we assume align has just happened. (when (called-interactively-p 'any) (org-table-align)) (let ((dests (orgtbl-gather-send-defs)) - (table (org-table-to-lisp - (buffer-substring-no-properties (org-table-begin) - (org-table-end)))) + (table (org-table-to-lisp)) (ntbl 0)) (unless dests (if maybe (throw 'exit nil) @@ -6096,7 +6174,7 @@ which will prompt for the width." ((numberp ask) ask) (t 12)))) ;; Skip any hline a the top of table. - (while (eq (car table) 'hline) (setq table (cdr table))) + (while (eq (car table) 'hline) (pop table)) ;; Skip table header if any. (dolist (x (or (cdr (memq 'hline table)) table)) (when (consp x) @@ -6122,7 +6200,7 @@ which will prompt for the width." ;; 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 +;; See https://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) @@ -6136,7 +6214,7 @@ which will prompt for the width." 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 + ;; https://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")) diff --git a/lisp/org/org-tempo.el b/lisp/org/org-tempo.el index 9ae2700549c..eac6b35fd50 100644 --- a/lisp/org/org-tempo.el +++ b/lisp/org/org-tempo.el @@ -4,7 +4,7 @@ ;; ;; Author: Rasmus Pank Roulund ;; Keywords: outlines, hypermedia, calendar, wp -;; Homepage: http://orgmode.org +;; Homepage: https://orgmode.org ;; ;; This file is part of GNU Emacs. ;; @@ -122,7 +122,7 @@ Go through `org-structure-template-alist' and (special (member name '("src" "export")))) (tempo-define-template (format "org-%s" (replace-regexp-in-string " " "-" name)) `(,(format "#+begin_%s%s" name (if special " " "")) - ,(when special 'p) '> n '> ,(unless special 'p) n + ,(when special 'p) '> n ,(unless special 'p) n ,(format "#+end_%s" (car (split-string name " "))) >) key diff --git a/lisp/org/org-timer.el b/lisp/org/org-timer.el index a1eb5e4a7a7..251e3c86b68 100644 --- a/lisp/org/org-timer.el +++ b/lisp/org/org-timer.el @@ -470,19 +470,18 @@ time is up." Try to use an Org header, otherwise use the buffer name." (cond ((derived-mode-p 'org-agenda-mode) - (let* ((marker (or (get-text-property (point) 'org-marker) - (org-agenda-error))) + (let* ((marker (or (get-text-property (point) 'org-marker))) (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))))))) + (when (and marker (marker-buffer 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)))) + (ignore-errors (org-get-heading))) (t (buffer-name (buffer-base-buffer))))) (provide 'org-timer) diff --git a/lisp/org/org-version.el b/lisp/org/org-version.el index 2a783871405..a5219a0e11b 100644 --- a/lisp/org/org-version.el +++ b/lisp/org/org-version.el @@ -5,13 +5,13 @@ (defun org-release () "The release version of Org. Inserted by installing Org mode or when a release is made." - (let ((org-release "9.3")) + (let ((org-release "9.4.1")) org-release)) ;;;###autoload (defun org-git-version () "The Git version of Org mode. Inserted by installing Org or when a release is made." - (let ((org-git-version "release_9.3")) + (let ((org-git-version "release_9.4.1-116-g353bb4")) org-git-version)) (provide 'org-version) diff --git a/lisp/org/org.el b/lisp/org/org.el index a7502d188e2..3db07cd89b3 100644 --- a/lisp/org/org.el +++ b/lisp/org/org.el @@ -4,10 +4,12 @@ ;; Copyright (C) 2004-2020 Free Software Foundation, Inc. ;; ;; Author: Carsten Dominik +;; Maintainer: Bastien Guerry ;; Keywords: outlines, hypermedia, calendar, wp ;; Homepage: https://orgmode.org -;; Version: 9.3 -;; + +;; Version: 9.4.1 + ;; This file is part of GNU Emacs. ;; ;; GNU Emacs is free software: you can redistribute it and/or modify @@ -121,9 +123,12 @@ Stars are put in group 1 and the trimmed body in group 2.") (declare-function org-archive-subtree-default "org-archive" ()) (declare-function org-archive-to-archive-sibling "org-archive" ()) (declare-function org-attach "org-attach" ()) +(declare-function org-attach-dir "org-attach" + (&optional create-if-not-exists-p no-fs-check)) (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-auto-clockout "org-clock" ()) (declare-function org-clock-cancel "org-clock" ()) (declare-function org-clock-display "org-clock" (&optional arg)) (declare-function org-clock-get-last-clock-out-time "org-clock" ()) @@ -141,6 +146,7 @@ Stars are put in group 1 and the trimmed body in group 2.") (declare-function org-clock-update-time-maybe "org-clock" ()) (declare-function org-clocking-buffer "org-clock" ()) (declare-function org-clocktable-shift "org-clock" (dir n)) +(declare-function org-columns-quit "org-colview" ()) (declare-function org-columns-insert-dblock "org-colview" ()) (declare-function org-duration-from-minutes "org-duration" (minutes &optional fmt canonical)) (declare-function org-element-at-point "org-element" ()) @@ -172,6 +178,7 @@ Stars are put in group 1 and the trimmed body in group 2.") (declare-function org-inlinetask-outline-regexp "org-inlinetask" ()) (declare-function org-inlinetask-toggle-visibility "org-inlinetask" ()) (declare-function org-latex-make-preamble "ox-latex" (info &optional template snippet?)) +(declare-function org-num-mode "org-num" (&optional arg)) (declare-function org-plot/gnuplot "org-plot" (&optional params)) (declare-function org-tags-view "org-agenda" (&optional todo-only match)) (declare-function org-timer "org-timer" (&optional restart no-insert)) @@ -189,6 +196,7 @@ Stars are put in group 1 and the trimmed body in group 2.") (defvar org-radio-target-regexp) (defvar org-target-link-regexp) (defvar org-target-regexp) +(defvar org-id-overriding-file-name) ;; load languages based on value of `org-babel-load-languages' (defvar org-babel-load-languages) @@ -215,15 +223,17 @@ and then loads the resulting file using `load-file'. With optional prefix argument COMPILE, the tangled Emacs Lisp file is byte-compiled before it is loaded." (interactive "fFile to load: \nP") - (let* ((tangled-file (concat (file-name-sans-extension file) ".el"))) + (let ((tangled-file (concat (file-name-sans-extension file) ".el"))) ;; Tangle only if the Org file is newer than the Elisp file. (unless (org-file-newer-than-p tangled-file - (file-attribute-modification-time (file-attributes file))) - (org-babel-tangle-file file tangled-file "emacs-lisp")) + (file-attribute-modification-time + (file-attributes (file-truename file)))) + (org-babel-tangle-file file tangled-file "emacs-lisp\\|elisp")) (if compile (progn - (byte-compile-file tangled-file 'load) + (byte-compile-file tangled-file) + (load tangled-file) (message "Compiled and loaded %s" tangled-file)) (load-file tangled-file) (message "Loaded %s" tangled-file)))) @@ -348,6 +358,14 @@ FULL is given." ;;; Syntax Constants +;;;; Comments +(defconst org-comment-regexp + (rx (seq bol (zero-or-more (any "\t ")) "#" (or " " eol))) + "Regular expression for comment lines.") + +;;;; Keyword +(defconst org-keyword-regexp "^[ \t]*#\\+\\(\\S-+?\\):[ \t]*\\(.*\\)$" + "Regular expression for keyword-lines.") ;;;; Block @@ -362,6 +380,65 @@ FULL is given." (defconst org-dblock-end-re "^[ \t]*#\\+\\(?:END\\|end\\)\\([: \t\r\n]\\|$\\)" "Matches the end of a dynamic block.") +;;;; Timestamp + +(defconst org-ts--internal-regexp + (rx (seq + (= 4 digit) "-" (= 2 digit) "-" (= 2 digit) + (optional " " (*? nonl)))) + "Regular expression matching the innards of a time stamp.") + +(defconst org-ts-regexp (format "<\\(%s\\)>" org-ts--internal-regexp) + "Regular expression for fast time stamp matching.") + +(defconst org-ts-regexp-inactive + (format "\\[\\(%s\\)\\]" org-ts--internal-regexp) + "Regular expression for fast inactive time stamp matching.") + +(defconst org-ts-regexp-both (format "[[<]\\(%s\\)[]>]" org-ts--internal-regexp) + "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.") + ;;;; Clock and Planning (defconst org-clock-string "CLOCK:" @@ -413,7 +490,7 @@ Matched keyword is in group 1.") (defconst org-deadline-time-hour-regexp (concat "\\<" org-deadline-string - " *<\\([^>]+[0-9]\\{1,2\\}:[0-9]\\{2\\}[0-9+:hdwmy \t.-]*\\)>") + " *<\\([^>]+[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 @@ -429,7 +506,7 @@ Matched keyword is in group 1.") (defconst org-scheduled-time-hour-regexp (concat "\\<" org-scheduled-string - " *<\\([^>]+[0-9]\\{1,2\\}:[0-9]\\{2\\}[0-9+:hdwmy \t.-]*\\)>") + " *<\\([^>]+[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 @@ -453,18 +530,6 @@ Matched keyword is in group 1.") " *[[<]\\([^]>]+\\)[]>]") "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 @@ -489,6 +554,12 @@ Group 1 contains drawer's name or \"END\".") (defconst org-clock-drawer-end-re "^[ \t]*:END:[ \t]*$" "Regular expression matching the last line of a clock drawer.") +(defconst org-logbook-drawer-re + (rx (seq bol (0+ (any "\t ")) ":LOGBOOK:" (0+ (any "\t ")) "\n" + (*? (0+ nonl) "\n") + (0+ (any "\t ")) ":END:" (0+ (any "\t ")) eol)) + "Matches an entire LOGBOOK drawer.") + (defconst org-property-drawer-re (concat "^[ \t]*:PROPERTIES:[ \t]*\n" "\\(?:[ \t]*:\\S-+:\\(?: .*\\)?[ \t]*\n\\)*?" @@ -560,60 +631,8 @@ An entry can be toggled between COMMENT and normal with (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.") - -;;;; 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.") +Effort estimates given in this property need to be in the format +defined in org-duration.el.") ;;; The custom variables @@ -644,6 +663,7 @@ After a match, group 1 contains the repeat expression.") (defvar org-modules-loaded nil "Have the modules been loaded already?") +;;;###autoload (defun org-load-modules-maybe (&optional force) "Load all extensions listed in `org-modules'." (when (or force (not org-modules-loaded)) @@ -852,6 +872,7 @@ cursor keys will then execute Org commands in the following contexts: - in a plain list item, changing the bullet type - in a property definition line, switching between allowed values - in the BEGIN line of a clock table (changing the time block). +- in a table, moving the cell in the specified direction. Outside these contexts, the commands will throw an error. When this variable is t and the cursor is not in a special @@ -861,9 +882,9 @@ cycling will no longer happen anywhere in an item line, but only 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: +will not be special in headlines, property lines, item lines, and +table cells, to make shift selection work there as well. If this is +what you 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]' \ @@ -879,7 +900,7 @@ will still edit the time stamp - this is just too good to give up." (const :tag "When outside special context" t) (const :tag "Everywhere except timestamps" always))) -(defcustom org-loop-over-headlines-in-active-region nil +(defcustom org-loop-over-headlines-in-active-region t "Shall some commands act upon headlines in the active region? When set to t, some commands will be performed in all headlines @@ -897,16 +918,19 @@ The list of commands is: `org-schedule', `org-deadline', `org-todo', `org-set-tags-command', `org-archive-subtree', `org-archive-set-tag', `org-toggle-archive-tag' and `org-archive-to-archive-sibling'. The archiving commands skip -already archived entries." +already archived entries. + +See `org-agenda-loop-over-headlines-in-active-region' for the +equivalent option for agenda views." :type '(choice (const :tag "Don't loop" nil) (const :tag "All headlines in active region" t) (const :tag "In active region, headlines at the same level than the first one" start-level) (string :tag "Tags/Property/Todo matcher")) - :version "24.1" + :package-version '(Org . "9.4") :group 'org-todo :group 'org-archive) -(defcustom org-startup-folded t +(defcustom org-startup-folded 'showeverything "Non-nil means entering Org mode will switch to OVERVIEW. This can also be configured on a per-file basis by adding one of @@ -921,6 +945,7 @@ 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 + :package-version '(Org . "9.4") :type '(choice (const :tag "nofold: show all" nil) (const :tag "fold: overview" t) @@ -937,7 +962,7 @@ 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 +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 @@ -955,13 +980,24 @@ the following lines anywhere in the buffer: (const :tag "Not" nil) (const :tag "Globally (slow on startup in large files)" t))) +(defcustom org-startup-numerated nil + "Non-nil means turn on `org-num-mode' on startup. +This can also be configured on a per-file basis by adding one of +the following lines anywhere in the buffer: + + #+STARTUP: num + #+STARTUP: nonum" + :group 'org-structure + :package-version '(Org . "9.4") + :type '(choice + (const :tag "Not" nil) + (const :tag "Globally" t))) + (defcustom org-use-sub-superscripts t "Non-nil means interpret \"_\" and \"^\" for display. If you want to control how Org exports those characters, see -`org-export-with-sub-superscripts'. `org-use-sub-superscripts' -used to be an alias for `org-export-with-sub-superscripts' in -Org <8.0, it is not anymore. +`org-export-with-sub-superscripts'. When this option is turned on, you can use TeX-like syntax for sub- and superscripts within the buffer. Several characters after @@ -1058,15 +1094,51 @@ use that string instead. 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." +effective. It cannot be set as a local variable." :group 'org-startup :type '(choice (const :tag "Default" nil) - (string :tag "String" :value "...#")) - :safe (lambda (v) (and (string-or-null-p v) (not (equal "" v))))) + (string :tag "String" :value "...#"))) (defvar org-display-table nil "The display table for Org mode, in case `org-ellipsis' is non-nil.") +(defcustom org-directory "~/org" + "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 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 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) + +(defcustom org-default-notes-file (convert-standard-filename "~/.notes") + "Default target for storing notes. +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 'file) + +(defcustom org-reverse-note-order nil + "Non-nil means store new notes at the beginning of a file or entry. +When nil, new notes will be filed to the end of a file or entry. +This can also be a list with cons cells of regular expressions that +are matched against file names, and values." + :group 'org-capture + :group 'org-refile + :type '(choice + (const :tag "Reverse always" t) + (const :tag "Reverse never" nil) + (repeat :tag "By file name regexp" + (cons regexp boolean)))) + (defgroup org-keywords nil "Keywords in Org mode." :tag "Org Keywords" @@ -1097,7 +1169,7 @@ effective." "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 +locations. As a consequence, Org always exposes a neighborhood around point. How much is shown depends on the initial action, or context. Valid contexts are @@ -1219,16 +1291,17 @@ See `org-file-apps'.") (defcustom org-file-apps '((auto-mode . emacs) + (directory . emacs) ("\\.mm\\'" . default) ("\\.x?html?\\'" . default) ("\\.pdf\\'" . default)) - "External applications for opening `file:path' items in a document. + "Applications for opening `file:path' items in a document. \\ -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. +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: @@ -1304,6 +1377,7 @@ For more examples, see the system specific constants `org-file-apps-windowsnt' `org-file-apps-gnu'." :group 'org + :package-version '(Org . "9.4") :type '(repeat (cons (choice :value "" (string :tag "Extension") @@ -1466,6 +1540,7 @@ the values `folded', `children', or `subtree'." :type 'hook) (defcustom org-cycle-hook '(org-cycle-hide-archived-subtrees + org-cycle-hide-drawers org-cycle-show-empty-lines org-optimize-window-after-visibility-change) "Hook that is run after `org-cycle' has changed the buffer visibility. @@ -1475,9 +1550,8 @@ 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 - :version "26.1" - :package-version '(Org . "8.3")) + :package-version '(Org . "9.4") + :type 'hook) (defgroup org-edit-structure nil "Options concerning structure editing in Org mode." @@ -1503,9 +1577,15 @@ lines to the buffer: (defcustom org-adapt-indentation t "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: +When this variable is set to t, Org assumes that you write +outlines by indenting text in each node to align with the +headline (after the stars). + +When this variable is set to 'headline-data, only adapt the +indentation of the data lines right below the headline, such as +planning/clock lines and property/logbook drawers. + +The following issues are influenced by this variable: - The indentation is increased by one space in a demotion command, and decreased by one in a promotion command. However, @@ -1517,14 +1597,18 @@ stars). The following issues are influenced by this variable: when this variable is set. When nil, they will not be indented. - TAB indents a line relative to current level. The lines below - a headline will be indented when this variable is set. + a headline will be indented when this variable is set to t. 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 + :type '(choice + (const :tag "Adapt indentation for all lines" t) + (const :tag "Adapt indentation for headline data lines" + 'headline-data) + (const :tag "Do not adapt indentation at all" nil)) :safe #'booleanp) (defvaralias 'org-special-ctrl-a 'org-special-ctrl-a/e) @@ -1572,16 +1656,15 @@ This may also be a cons cell where the behavior for `C-a' and When nil, `C-k' will call the default `kill-line' command. When t, the following will happen while the cursor is in the headline: -- When the cursor is at the beginning of a headline, kill the entire - line and possible the folded subtree below the line. -- When in the middle of the headline text, kill the headline up to the tags. -- When after the headline text, kill the tags." +- When at the beginning of a headline, kill the entire subtree. +- When in the middle of the headline text, kill the text up to the tags. +- When after the headline text and before the tags, kill all the tags." :group 'org-edit-structure :type 'boolean) (defcustom org-ctrl-k-protect-subtree nil - "Non-nil means, do not delete a hidden subtree with C-k. -When set to the symbol `error', simply throw an error when C-k is + "Non-nil means, do not delete a hidden subtree with `C-k'. +When set to the symbol `error', simply throw an error when `C-k' is used to kill (part-of) a headline that has hidden text behind it. Any other non-nil value will result in a query to the user, if it is OK to kill that hidden subtree. When nil, kill without remorse." @@ -1786,213 +1869,6 @@ Changing this requires a restart of Emacs to work correctly." :group 'org-link-follow :type 'integer) -(defgroup org-refile nil - "Options concerning refiling entries in Org mode." - :tag "Org Refile" - :group 'org) - -(defcustom org-directory "~/org" - "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 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 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) - -(defcustom org-default-notes-file (convert-standard-filename "~/.notes") - "Default target for storing notes. -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 'file) - -(defcustom org-reverse-note-order nil - "Non-nil means store new notes at the beginning of a file or entry. -When nil, new notes will be filed to the end of a file or entry. -This can also be a list with cons cells of regular expressions that -are matched against file names, and values." - :group 'org-capture - :group 'org-refile - :type '(choice - (const :tag "Reverse always" t) - (const :tag "Reverse never" nil) - (repeat :tag "By file name regexp" - (cons regexp boolean)))) - -(defcustom org-log-refile nil - "Information to record when a task is refiled. - -Possible values are: - -nil Don't add anything -time Add a time stamp to the task -note Prompt for a note and add it with template `org-log-note-headings' - -This option can also be set with on a per-file-basis with - - #+STARTUP: nologrefile - #+STARTUP: logrefile - #+STARTUP: lognoterefile - -You can have local logging settings for a subtree by setting the LOGGING -property to one or more of these keywords. - -When bulk-refiling, e.g., from the agenda, the value `note' is -forbidden and will temporarily be changed to `time'." - :group 'org-refile - :group 'org-progress - :version "24.1" - :type '(choice - (const :tag "No logging" nil) - (const :tag "Record timestamp" time) - (const :tag "Record timestamp with note." note))) - -(defcustom org-refile-targets nil - "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 - a file name or a list of file names. If you use `org-agenda-files' for - that, all agenda files will be scanned for targets. Nil means consider - headings in the current buffer. -- A specification of how to find candidate refile targets. This may be - any of: - - a cons cell (:tag . \"TAG\") to identify refile targets by a tag. - This tag has to be present in all target headlines, inheritance will - not be considered. - - a cons cell (:todo . \"KEYWORD\") to identify refile targets by - todo keyword. - - a cons cell (:regexp . \"REGEXP\") with a regular expression matching - headlines that are refiling targets. - - a cons cell (:level . N). Any headline of level N is considered a target. - Note that, when `org-odd-levels-only' is set, level corresponds to - order in hierarchy, not to the number of stars. - - a cons cell (:maxlevel . N). Any headline with level <= N is a target. - Note that, when `org-odd-levels-only' is set, level corresponds to - order in hierarchy, not to the number of stars. - -Each element of this list generates a set of possible targets. -The union of these sets is presented (with completion) to -the user by `org-refile'. - -You can set the variable `org-refile-target-verify-function' to a function -to verify each headline found by the simple criteria above. - -When this variable is nil, all top-level headlines in the current buffer -are used, equivalent to the value `((nil . (:level . 1))'." - :group 'org-refile - :type '(repeat - (cons - (choice :value org-agenda-files - (const :tag "All agenda files" org-agenda-files) - (const :tag "Current buffer" nil) - (function) (variable) (file)) - (choice :tag "Identify target headline by" - (cons :tag "Specific tag" (const :value :tag) (string)) - (cons :tag "TODO keyword" (const :value :todo) (string)) - (cons :tag "Regular expression" (const :value :regexp) (regexp)) - (cons :tag "Level number" (const :value :level) (integer)) - (cons :tag "Max Level number" (const :value :maxlevel) (integer)))))) - -(defcustom org-refile-target-verify-function nil - "Function to verify if the headline at point should be a refile target. -The function will be called without arguments, with point at the -beginning of the headline. It should return t and leave point -where it is if the headline is a valid target for refiling. - -If the target should not be selected, the function must return nil. -In addition to this, it may move point to a place from where the search -should be continued. For example, the function may decide that the entire -subtree of the current entry should be excluded and move point to the end -of the subtree." - :group 'org-refile - :type '(choice - (const nil) - (function))) - -(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 \\[org-refile]' or, -if you find that easier, \ -`\\[universal-argument] \\[universal-argument] \\[universal-argument] \ -\\[org-refile]'." - :group 'org-refile - :version "24.1" - :type 'boolean) - -(defcustom org-refile-use-outline-path nil - "Non-nil means provide refile targets as paths. -So a level 3 headline will be available as level1/level2/level3. - -When the value is `file', also include the file name (without directory) -into the path. In this case, you can also stop the completion after -the file name, to get entries inserted as top level in the file. - -When `full-file-path', include the full file path. - -When `buffer-name', use the buffer name." - :group 'org-refile - :type '(choice - (const :tag "Not" nil) - (const :tag "Yes" t) - (const :tag "Start with file name" file) - (const :tag "Start with full file path" full-file-path) - (const :tag "Start with buffer name" buffer-name))) - -(defcustom org-outline-path-complete-in-steps t - "Non-nil means complete the outline path in hierarchical steps. -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) - -(defcustom org-refile-allow-creating-parent-nodes nil - "Non-nil means allow the creation of new nodes as refile targets. -New nodes are then created by adding \"/new node name\" to the completion -of an existing node. When the value of this variable is `confirm', -new node creation must be confirmed by the user (recommended). -When nil, the completion must match an existing entry. - -Note that, if the new heading is not seen by the criteria -listed in `org-refile-targets', multiple instances of the same -heading would be created by trying again to file under the new -heading." - :group 'org-refile - :type '(choice - (const :tag "Never" nil) - (const :tag "Always" t) - (const :tag "Prompt for confirmation" confirm))) - -(defcustom org-refile-active-region-within-subtree nil - "Non-nil means also refile active region within a subtree. - -By default `org-refile' doesn't allow refiling regions if they -don't contain a set of subtrees, but it might be convenient to -do so sometimes: in that case, the first line of the region is -converted to a headline before refiling." - :group 'org-refile - :version "24.1" - :type 'boolean) - (defgroup org-todo nil "Options concerning TODO items in Org mode." :tag "Org TODO" @@ -2548,53 +2424,69 @@ property to one or more of these keywords." :tag "Org Priorities" :group 'org-todo) -(defcustom org-enable-priority-commands t +(defvaralias 'org-enable-priority-commands 'org-priority-enable-commands) +(defcustom org-priority-enable-commands t "Non-nil means priority commands are active. When nil, these commands will be disabled, so that you never accidentally set a priority." :group 'org-priorities :type 'boolean) -(defcustom org-highest-priority ?A - "The highest priority of TODO items. A character like ?A, ?B etc. -Must have a smaller ASCII number than `org-lowest-priority'." - :group 'org-priorities - :type 'character) +(defvaralias 'org-highest-priority 'org-priority-highest) -(defcustom org-lowest-priority ?C - "The lowest priority of TODO items. A character like ?A, ?B etc. -Must have a larger ASCII number than `org-highest-priority'." +(defcustom org-priority-highest ?A + "The highest priority of TODO items. +A character like ?A, ?B, etc., or a numeric value like 1, 2, etc. +Must be smaller than `org-priority-lowest'." + :group 'org-priorities + :type '(choice + (character :tag "Character") + (integer :tag "Integer (< 65)"))) + +(defvaralias 'org-lowest-priority 'org-priority-lowest) +(defcustom org-priority-lowest ?C + "The lowest priority of TODO items. +A character like ?A, ?B, etc., or a numeric value like 1, 2, etc. +Must be higher than `org-priority-highest'." :group 'org-priorities - :type 'character) + :type '(choice + (character :tag "Character") + (integer :tag "Integer (< 65)"))) -(defcustom org-default-priority ?B +(defvaralias 'org-default-priority 'org-priority-default) +(defcustom org-priority-default ?B "The default priority of TODO items. This is the priority an item gets if no explicit priority is given. When starting to cycle on an empty priority the first step in the cycle depends on `org-priority-start-cycle-with-default'. The resulting first -step priority must not exceed the range from `org-highest-priority' to -`org-lowest-priority' which means that `org-default-priority' has to be -in this range exclusive or inclusive the range boundaries. Else the -first step refuses to set the default and the second will fall back -to (depending on the command used) the highest or lowest priority." +step priority must not exceed the range from `org-priority-highest' to +`org-priority-lowest' which means that `org-priority-default' has to be +in this range exclusive or inclusive to the range boundaries. Else the +first step refuses to set the default and the second will fall back on +\(depending on the command used) the highest or lowest priority." :group 'org-priorities - :type 'character) + :type '(choice + (character :tag "Character") + (integer :tag "Integer (< 65)"))) (defcustom org-priority-start-cycle-with-default t "Non-nil means start with default priority when starting to cycle. When this is nil, the first step in the cycle will be (depending on the command used) one higher or lower than the default priority. -See also `org-default-priority'." +See also `org-priority-default'." :group 'org-priorities :type 'boolean) -(defcustom org-get-priority-function nil +(defvaralias 'org-get-priority-function 'org-priority-get-priority-function) +(defcustom org-priority-get-priority-function nil "Function to extract the priority from a string. -The string is normally the headline. If this is nil Org computes the -priority from the priority cookie like [#A] in the headline. It returns -an integer, increasing by 1000 for each priority level. -The user can set a different function here, which should take a string -as an argument and return the numeric priority." +The string is normally the headline. If this is nil, Org +computes the priority from the priority cookie like [#A] in the +headline. It returns an integer, increasing by 1000 for each +priority level. + +The user can set a different function here, which should take a +string as an argument and return the numeric priority." :group 'org-priorities :version "24.1" :type '(choice @@ -2767,7 +2659,9 @@ stamps outside of this range." (defcustom org-read-date-display-live t "Non-nil means display current interpretation of date prompt live. -This display will be in an overlay, in the minibuffer." +This display will be in an overlay, in the minibuffer. Note that +live display is only active when `org-read-date-popup-calendar' +is non-nil." :group 'org-time :type 'boolean) @@ -2944,7 +2838,7 @@ automatically if necessary." When nil, you have to press RET to exit it. During fast tag selection, you can toggle this flag with `C-c'. This variable can also have the value `expert'. In this case, the window -displaying the tags menu is not even shown, until you press C-c again." +displaying the tags menu is not even shown, until you press `C-c' again." :group 'org-tags :type '(choice (const :tag "No" nil) @@ -3180,8 +3074,13 @@ This list will be combined with the constant `org-global-properties-fixed'. The entries in this list are cons cells where the car is a property name and cdr is a string with the value. -You can set buffer-local values for the same purpose in the variable -`org-file-properties' this by adding lines like +Buffer local properties are added either by a document property drawer + +:PROPERTIES: +:NAME: VALUE +:END: + +or by adding lines like #+PROPERTY: NAME VALUE" :group 'org-properties @@ -3189,10 +3088,15 @@ You can set buffer-local values for the same purpose in the variable (cons (string :tag "Property") (string :tag "Value")))) -(defvar-local org-file-properties nil - "List of property/value pairs that can be inherited by any entry. -Valid for the current buffer. -This variable is populated from #+PROPERTY lines.") +(defvar-local org-keyword-properties nil + "List of property/value pairs inherited by any entry. + +Valid for the current buffer. This variable is populated from +PROPERTY keywords. + +Note that properties are defined also in property drawers. +Properties defined there take precedence over properties defined +as keywords.") (defgroup org-agenda nil "Options concerning agenda views in Org mode." @@ -3201,11 +3105,18 @@ This variable is populated from #+PROPERTY lines.") (defvar-local org-category nil "Variable used by Org files to set a category for agenda display. -Such files should use a file variable to set it, for example +There are multiple ways to set the category. One way is to set +it in the document property drawer. For example: + +:PROPERTIES: +:CATEGORY: ELisp +:END: + +Other ways to define it is as an emacs file variable, for example # -*- mode: org; org-category: \"ELisp\" -or contain a special line +or for the file to contain a special line: #+CATEGORY: ELisp @@ -3266,16 +3177,6 @@ A nil value means to remove them, after a query, from the list." :group 'org-agenda :type 'boolean) -(defcustom org-agenda-diary-file 'diary-file - "File to which to add new entries with the `i' key in agenda and calendar. -When this is the symbol `diary-file', the functionality in the Emacs -calendar will be used to add entries to the `diary-file'. But when this -points to a file, `org-agenda-diary-entry' will be used instead." - :group 'org-agenda - :type '(choice - (const :tag "The standard Emacs diary file" diary-file) - (file :tag "Special Org file diary entries"))) - (defgroup org-latex nil "Options for embedding LaTeX code into Org mode." :tag "Org LaTeX" @@ -3349,6 +3250,22 @@ When using LaTeXML set this option to (const :tag "None" nil) (string :tag "\nShell command"))) +(defcustom org-latex-to-html-convert-command nil + "Command to convert LaTeX fragments to HTML. +This command is very open-ended: the output of the command will +directly replace the LaTeX fragment in the resulting HTML. +Replace format-specifiers in the command as noted below and use +`shell-command' to convert LaTeX to HTML. +%i: The LaTeX fragment to be converted. + +For example, this could be used with LaTeXML as +\"latexmlc 'literal:%i' --profile=math --preload=siunitx.sty 2>/dev/null\"." + :group 'org-latex + :package-version '(Org . "9.4") + :type '(choice + (const :tag "None" nil) + (string :tag "Shell command"))) + (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 @@ -3667,12 +3584,23 @@ hide them with `org-toggle-custom-properties-visibility'." :version "24.3" :type '(repeat (string :tag "Property Name"))) -(defcustom org-fontify-done-headline nil +(defcustom org-fontify-todo-headline nil + "Non-nil means change the face of a headline if it is marked as TODO. +Normally, only the TODO/DONE keyword indicates the state of a headline. +When this is non-nil, the headline after the keyword is set to the +`org-headline-todo' as an additional indication." + :group 'org-appearance + :package-version '(Org . "9.4") + :type 'boolean + :safe t) + +(defcustom org-fontify-done-headline t "Non-nil means change the face of a headline if it is marked DONE. Normally, only the TODO/DONE keyword indicates the state of a headline. When this is non-nil, the headline after the keyword is set to the `org-headline-done' as an additional indication." :group 'org-appearance + :package-version '(Org . "9.4") :type 'boolean) (defcustom org-fontify-emphasized-text t @@ -3774,7 +3702,7 @@ After a match, the match groups contain these elements: ;; This used to be a defcustom (Org <8.0) but allowing the users to ;; set this option proved cumbersome. See this message/thread: -;; http://article.gmane.org/gmane.emacs.orgmode/68681 +;; https://orgmode.org/list/B72CDC2B-72F6-43A8-AC70-E6E6295766EC@gmail.com (defvar org-emphasis-regexp-components '("-[:space:]('\"{" "-[:space:].,:!?;'\")}\\[" "[:space:]" "." 1) "Components used to build the regular expression for emphasis. @@ -3920,6 +3848,14 @@ If yes, offer to stop it and to save the buffer with the changes." (add-hook 'org-mode-hook 'org-clock-load) (add-hook 'kill-emacs-hook 'org-clock-save)) +(defun org-clock-auto-clockout-insinuate () + "Set up hook for auto clocking out when Emacs is idle. +See `org-clock-auto-clockout-timer'. + +This function is meant to be added to the user configuration." + (require 'org-clock) + (add-hook 'org-clock-in-hook #'org-clock-auto-clockout t)) + (defgroup org-archive nil "Options concerning archiving in Org mode." :tag "Org Archive" @@ -3973,14 +3909,13 @@ Here are a few examples: Archive in file ./basement (relative path), as level 3 trees below the level 2 heading \"** Finished Tasks\". -You may set this option on a per-file basis by adding to the buffer a -line like +You may define it locally by setting an ARCHIVE property. If +such a property is found in the file or in an entry, and anywhere +up the hierarchy, it will be used. -#+ARCHIVE: basement::** Finished Tasks +You can also set it for the whole file using the keyword-syntax: -You may also define it locally for a subtree by setting an ARCHIVE property -in the entry. If such a property is found in an entry, or anywhere up -the hierarchy, it will be used." +#+ARCHIVE: basement::** Finished Tasks" :group 'org-archive :type 'string) @@ -4189,6 +4124,8 @@ After a match, the following groups carry important information: ("content" org-startup-folded content) ("indent" org-startup-indented t) ("noindent" org-startup-indented nil) + ("num" org-startup-numerated t) + ("nonum" org-startup-numerated nil) ("hidestars" org-hide-leading-stars t) ("showstars" org-hide-leading-stars nil) ("odd" org-odd-levels-only t) @@ -4302,72 +4239,112 @@ See `org-tag-alist' for their structure." ;; Preserve order of ALIST1. (append (nreverse to-add) alist2))))) +(defun org-priority-to-value (s) + "Convert priority string S to its numeric value." + (or (save-match-data + (and (string-match "\\([0-9]+\\)" s) + (string-to-number (match-string 1 s)))) + (string-to-char s))) + (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) - (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"))))))) + (let ((alist (org-collect-keywords + (append '("FILETAGS" "TAGS") + (and (not tags-only) + '("ARCHIVE" "CATEGORY" "COLUMNS" "CONSTANTS" + "LINK" "OPTIONS" "PRIORITIES" "PROPERTY" + "SEQ_TODO" "STARTUP" "TODO" "TYP_TODO"))) + '("ARCHIVE" "CATEGORY" "COLUMNS" "PRIORITIES")))) ;; Startup options. Get this early since it does change ;; behavior for other options (e.g., tags). - (let ((startup (cdr (assq 'startup alist)))) + (let ((startup (cl-mapcan (lambda (value) (split-string value)) + (cdr (assoc "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))))))) + (pcase (assoc-string option org-startup-options t) + (`(,_ ,variable ,value t) + (unless (listp (symbol-value variable)) + (set (make-local-variable variable) nil)) + (add-to-list variable value)) + (`(,_ ,variable ,value . ,_) + (set (make-local-variable variable) value)) + (_ nil)))) (setq-local org-file-tags (mapcar #'org-add-prop-inherited - (cdr (assq 'filetags alist)))) + (cl-mapcan (lambda (value) + (cl-mapcan + (lambda (k) (org-split-string k ":")) + (split-string value))) + (cdr (assoc "FILETAGS" alist))))) (setq org-current-tag-alist (org--tag-add-to-alist org-tag-persistent-alist - (let ((tags (cdr (assq 'tags alist)))) - (if tags (org-tag-string-to-alist tags) + (let ((tags (cdr (assoc "TAGS" alist)))) + (if tags + (org-tag-string-to-alist + (mapconcat #'identity tags "\n")) 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))) + ;; Properties. + (let ((properties nil)) + (dolist (value (cdr (assoc "PROPERTY" alist))) + (when (string-match "\\(\\S-+\\)[ \t]+\\(.*\\)" value) + (setq properties (org--update-property-plist + (match-string-no-properties 1 value) + (match-string-no-properties 2 value) + properties)))) + (setq-local org-keyword-properties properties)) ;; Archive location. - (let ((archive (cdr (assq 'archive alist)))) + (let ((archive (cdr (assoc "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 + (let ((category (cdr (assoc "CATEGORY" alist)))) + (when category + (setq-local org-category (intern category)) + (setq-local org-keyword-properties (org--update-property-plist - "CATEGORY" cat org-file-properties)))) + "CATEGORY" category org-keyword-properties)))) ;; Columns. - (let ((column (cdr (assq 'columns alist)))) + (let ((column (cdr (assoc "COLUMNS" alist)))) (when column (setq-local org-columns-default-format column))) ;; Constants. - (setq org-table-formula-constants-local (cdr (assq 'constants alist))) + (let ((store nil)) + (dolist (pair (cl-mapcan #'split-string + (cdr (assoc "CONSTANTS" alist)))) + (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))))) + (setq org-table-formula-constants-local store)) ;; Link abbreviations. - (let ((links (cdr (assq 'link alist)))) + (let ((links + (delq nil + (mapcar + (lambda (value) + (and (string-match "\\`\\(\\S-+\\)[ \t]+\\(.+\\)" value) + (cons (match-string-no-properties 1 value) + (match-string-no-properties 2 value)))) + (cdr (assoc "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)))) + (let ((value (cdr (assoc "PRIORITIES" alist)))) + (pcase (and value (split-string value)) + (`(,high ,low ,default . ,_) + (setq-local org-priority-highest (org-priority-to-value high)) + (setq-local org-priority-lowest (org-priority-to-value low)) + (setq-local org-priority-default (org-priority-to-value default))))) ;; Scripts. - (let ((scripts (assq 'scripts alist))) - (when scripts - (setq-local org-use-sub-superscripts (cdr scripts)))) + (let ((value (cdr (assoc "OPTIONS" alist)))) + (dolist (option value) + (when (string-match "\\^:\\(t\\|nil\\|{}\\)" option) + (setq-local org-use-sub-superscripts + (read (match-string 1 option)))))) ;; TODO keywords. (setq-local org-todo-kwd-alist nil) (setq-local org-todo-key-alist nil) @@ -4378,7 +4355,13 @@ related expressions." (setq-local org-todo-sets nil) (setq-local org-todo-log-states nil) (let ((todo-sequences - (or (nreverse (cdr (assq 'todo alist))) + (or (append (mapcar (lambda (value) + (cons 'type (split-string value))) + (cdr (assoc "TYP_TODO" alist))) + (mapcar (lambda (value) + (cons 'sequence (split-string value))) + (append (cdr (assoc "TODO" alist)) + (cdr (assoc "SEQ_TODO" alist))))) (let ((d (default-value 'org-todo-keywords))) (if (not (stringp (car d))) d ;; XXX: Backward compatibility code. @@ -4463,109 +4446,90 @@ related expressions." "[ \t]*$")) (org-compute-latex-and-related-regexp))))) -(defun org--setup-collect-keywords (regexp &optional files alist) - "Return setup keywords values as an alist. +(defun org-collect-keywords (keywords &optional unique directory) + "Return values for KEYWORDS in current buffer, 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. +KEYWORDS is a list of strings. Return value is a list of +elements with the pattern: -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 (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 ":")) - (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 (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) (split-string value))) - (push (cons 'startup (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) - (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-strip-quotes 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. + (NAME . LIST-OF-VALUES) + +where NAME is the upcase name of the keyword, and LIST-OF-VALUES +is a list of non-empty values, as strings, in order of appearance +in the buffer. + +When KEYWORD appears in UNIQUE list, LIST-OF-VALUE is its first +value, empty or not, appearing in the buffer, as a string. + +When KEYWORD appears in DIRECTORIES, each value is a cons cell: + + (VALUE . DIRECTORY) + +where VALUE is the regular value, and DIRECTORY is the variable +`default-directory' for the buffer containing the keyword. This +is important for values containing relative file names, since the +function follows SETUPFILE keywords, and may change its working +directory." + (let* ((keywords (cons "SETUPFILE" (mapcar #'upcase keywords))) + (unique (mapcar #'upcase unique)) + (alist (org--collect-keywords-1 + keywords unique directory + (and buffer-file-name (list buffer-file-name)) + nil))) + ;; Re-order results. + (dolist (entry alist) + (pcase entry + (`(,_ . ,(and value (pred consp))) + (setcdr entry (nreverse value))))) + (nreverse alist))) + +(defun org--collect-keywords-1 (keywords unique directory files alist) + (org-with-point-at 1 + (let ((case-fold-search t) + (regexp (org-make-options-regexp keywords))) + (while (and keywords (re-search-forward regexp nil t)) + (let ((element (org-element-at-point))) + (when (eq 'keyword (org-element-type element)) + (let ((value (org-element-property :value element))) + (pcase (org-element-property :key element) + ("SETUPFILE" + (when (and (org-string-nw-p value) + (not buffer-read-only)) ;FIXME: bug in Gnus? + (let* ((uri (org-strip-quotes value)) + (uri-is-url (org-file-url-p uri)) + (uri (if uri-is-url + uri + (expand-file-name uri)))) + (unless (member uri files) + (with-temp-buffer + (unless uri-is-url + (setq default-directory (file-name-directory uri))) + (let ((contents (org-file-contents uri :noerror))) + (when contents + (insert contents) + ;; Fake Org mode: `org-element-at-point' + ;; doesn't need full set-up. (let ((major-mode 'org-mode)) - (org--setup-collect-keywords - regexp (cons f files) alist))))))))))))))) - alist) + (setq alist + (org--collect-keywords-1 + keywords unique directory + (cons uri files) + alist)))))))))) + (keyword + (let ((entry (assoc keyword alist)) + (final + (cond ((not (member keyword directory)) value) + (buffer-file-name + (cons value + (file-name-directory buffer-file-name))) + (t (cons value default-directory))))) + (cond ((member keyword unique) + (push (cons keyword final) alist) + (setq keywords (remove keyword keywords)) + (setq regexp (org-make-options-regexp keywords))) + ((null entry) (push (list keyword final) alist)) + (t (push final (cdr entry))))))))))) + alist))) (defun org-tag-string-to-alist (s) "Return tag alist associated to string S. @@ -4677,7 +4641,7 @@ already cached in the `org--file-cache' hash table, the download step is skipped. If NOERROR is non-nil, ignore the error when unable to read the FILE -from file or URL. +from file or URL, and return nil. If NOCACHE is non-nil, do a fresh fetch of FILE even if cached version is available. This option applies only if FILE is a URL." @@ -4701,7 +4665,8 @@ is available. This option applies only if FILE is a URL." org--file-cache) (funcall (if noerror #'message #'user-error) "Unable to fetch file from %S" - file)))) + file) + nil))) (t (with-temp-buffer (condition-case nil @@ -4711,7 +4676,8 @@ is available. This option applies only if FILE is a URL." (file-error (funcall (if noerror #'message #'user-error) "Unable to read file %S" - file)))))))) + file) + nil))))))) (defun org-extract-log-state-settings (x) "Extract the log state setting from a TODO keyword string. @@ -4790,12 +4756,9 @@ This is for getting out of special buffers like capture.") (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 (require 'org-entities) -;; (require 'org-compat) moved higher up in the file before it is first used (require 'org-faces) (require 'org-list) (require 'org-pcomplete) @@ -4829,7 +4792,6 @@ The following commands are available: (org-install-agenda-files-menu) (when org-link-descriptive (add-to-invisibility-spec '(org-link))) (add-to-invisibility-spec '(org-hide-block . t)) - (add-to-invisibility-spec '(org-hide-drawer . t)) (setq-local outline-regexp org-outline-regexp) (setq-local outline-level 'org-outline-level) (setq bidi-paragraph-direction 'left-to-right) @@ -4905,10 +4867,6 @@ The following commands are available: (regexp . "^[ \t]*#\\+[A-Z_]+:\\(\\s-*\\)\\S-+") (modes . '(org-mode))))) - ;; Make isearch reveal context - (setq-local outline-isearch-open-invisible-function - (lambda (&rest _) (org-show-context 'isearch))) - ;; Setup the pcomplete hooks (setq-local pcomplete-command-completion-function #'org-pcomplete-initial) (setq-local pcomplete-command-name-function #'org-command-at-point) @@ -4940,11 +4898,20 @@ The following commands are available: (when org-startup-with-latex-preview (org-latex-preview '(16))) (unless org-inhibit-startup-visibility-stuff (org-set-startup-visibility)) (when org-startup-truncated (setq truncate-lines t)) + (when org-startup-numerated (require 'org-num) (org-num-mode 1)) (when org-startup-indented (require 'org-indent) (org-indent-mode 1)))) + + ;; Activate `org-table-header-line-mode' + (when org-table-header-line-p + (org-table-header-line-mode 1)) ;; Try to set `org-hide' face correctly. (let ((foreground (org-find-invisible-foreground))) (when foreground - (set-face-foreground 'org-hide foreground)))) + (set-face-foreground 'org-hide foreground))) + ;; Set face extension as requested. + (org--set-faces-extend '(org-block-begin-line org-block-end-line) + org-fontify-whole-block-delimiter-line) + (org--set-faces-extend org-level-faces org-fontify-whole-heading-line)) ;; Update `customize-package-emacs-version-alist' (add-to-list 'customize-package-emacs-version-alist @@ -4955,7 +4922,9 @@ The following commands are available: ("8.3" . "26.1") ("9.0" . "26.1") ("9.1" . "26.1") - ("9.2" . "27.1"))) + ("9.2" . "27.1") + ("9.3" . "27.1") + ("9.4" . "27.2"))) (defvar org-mode-transpose-word-syntax-table (let ((st (make-syntax-table text-mode-syntax-table))) @@ -5004,8 +4973,6 @@ the rounding returns a past time." ;;;; Font-Lock stuff, including the activators -(require 'font-lock) - (defconst org-match-sexp-depth 3 "Number of stacked braces for sub/superscript matching.") @@ -5076,9 +5043,10 @@ stacked delimiters is N. Escaping delimiters is not possible." ;; Do not span over cells in table rows. (not (and (save-match-data (org-match-line "[ \t]*|")) (string-match-p "|" (match-string 4)))))) - (pcase-let ((`(,_ ,face ,_) (assoc marker org-emphasis-alist))) + (pcase-let ((`(,_ ,face ,_) (assoc marker org-emphasis-alist)) + (m (if org-hide-emphasis-markers 4 2))) (font-lock-prepend-text-property - (match-beginning 2) (match-end 2) 'face face) + (match-beginning m) (match-end m) 'face face) (when verbatim? (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0)) @@ -5086,7 +5054,8 @@ stacked delimiters is N. Escaping delimiters is not possible." '(display t invisible t intangible t))) (add-text-properties (match-beginning 2) (match-end 2) '(font-lock-multiline t org-emphasis t)) - (when org-hide-emphasis-markers + (when (and org-hide-emphasis-markers + (not (org-at-comment-p))) (add-text-properties (match-end 4) (match-beginning 5) '(invisible org-link)) (add-text-properties (match-beginning 3) (match-end 3) @@ -5249,13 +5218,23 @@ by a #." "Fontify #+ lines and blocks." (let ((case-fold-search t)) (when (re-search-forward - "^\\([ \t]*#\\(\\(\\+[a-zA-Z]+:?\\| \\|$\\)\\(_\\([a-zA-Z]+\\)\\)?\\)[ \t]*\\(\\([^ \t\n]*\\)[ \t]*\\(.*\\)\\)\\)" + (rx bol (group (zero-or-more (any " \t")) "#" + (group (group (or (seq "+" (one-or-more (any "a-zA-Z")) (optional ":")) + (any " \t") + eol)) + (optional (group "_" (group (one-or-more (any "a-zA-Z")))))) + (zero-or-more (any " \t")) + (group (group (zero-or-more (not (any " \t\n")))) + (zero-or-more (any " \t")) + (group (zero-or-more any))))) limit t) (let ((beg (match-beginning 0)) (end-of-beginline (match-end 0)) - (block-start (match-end 0)) ; includes the \n at end of #+begin line - (block-end nil) ; will include \n after end of block content - (lang (match-string 7)) ; the language, if it is an src block + ;; Including \n at end of #+begin line will include \n + ;; after the end of block content. + (block-start (match-end 0)) + (block-end nil) + (lang (match-string 7)) ; The language, if it is a source block. (bol-after-beginline (line-beginning-position 2)) (dc1 (downcase (match-string 2))) (dc3 (downcase (match-string 3))) @@ -5265,15 +5244,22 @@ by a #." ((and (match-end 4) (equal dc3 "+begin")) ;; Truly a block (setq block-type (downcase (match-string 5)) - quoting (member block-type org-protecting-blocks)) ; src, example, export, maybe more + ;; Src, example, export, maybe more. + 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 - ;; We do have a matching #+end line + (rx-to-string `(group bol (or (seq (one-or-more "*") space) + (seq (zero-or-more (any " \t")) + "#+end" + ,(match-string 4) + word-end + (zero-or-more any))))) + ;; We look further than LIMIT on purpose. + nil t) + ;; We do have a matching #+end line. (setq beg-of-endline (match-beginning 0) end-of-endline (match-end 0) nl-before-endline (1- (match-beginning 0))) - (setq block-end (match-beginning 0)) ; includes the final newline. + (setq block-end (match-beginning 0)) ; Include the final newline. (when quoting (org-remove-flyspell-overlays-in bol-after-beginline nl-before-endline) (remove-text-properties beg end-of-endline @@ -5306,10 +5292,14 @@ by a #." (add-text-properties beg (if whole-blockline bol-after-beginline end-of-beginline) '(face org-block-begin-line)) - (add-text-properties - beg-of-endline - (min (point-max) (if whole-blockline (min (point-max) (1+ end-of-endline)) end-of-endline)) - '(face org-block-end-line)) + (unless (eq (char-after beg-of-endline) ?*) + (add-text-properties + beg-of-endline + (if whole-blockline + (let ((beg-of-next-line (1+ end-of-endline))) + (min (point-max) beg-of-next-line)) + (min (point-max) end-of-endline)) + '(face org-block-end-line))) t)) ((member dc1 '("+title:" "+author:" "+email:" "+date:")) (org-remove-flyspell-overlays-in @@ -5329,22 +5319,26 @@ by a #." (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. + ;; Handle short captions (save-excursion (beginning-of-line) - (looking-at "\\([ \t]*#\\+caption\\(?:\\[.*\\]\\)?:\\)[ \t]*")) + (looking-at (rx (group (zero-or-more (any " \t")) + "#+caption" + (optional "[" (zero-or-more any) "]") + ":") + (zero-or-more (any " \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 '(" " "")) - ; Just a comment, the plus was not there + ;; Just a comment, the plus was not there (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 + (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)) @@ -5355,9 +5349,8 @@ by a #." (defun org-fontify-drawers (limit) "Fontify drawers." (when (re-search-forward org-drawer-regexp limit t) - (add-text-properties - (line-beginning-position) (line-beginning-position 2) - '(font-lock-fontified t face org-drawer)) + (add-text-properties (1- (match-beginning 1)) (1+ (match-end 1)) + '(font-lock-fontified t face org-drawer)) (org-remove-flyspell-overlays-in (line-beginning-position) (line-beginning-position 2)) t)) @@ -5385,8 +5378,8 @@ by a #." (end-re "\\(\\\\\\]\\|\\(#\\+end_\\|\\\\end{\\)\\S-+\\)") (extend (lambda (r1 r2 dir) (let ((re (replace-regexp-in-string "\\(begin\\|end\\)" r1 - (replace-regexp-in-string "[][]" r2 - (match-string-no-properties 0))))) + (replace-regexp-in-string "[][]" r2 + (match-string-no-properties 0))))) (re-search-forward (regexp-quote re) nil t dir))))) (save-match-data (save-excursion @@ -5482,33 +5475,46 @@ Result depends on variable `org-highlight-latex-and-related'." (append re-latex re-entities re-sub) "\\|")))) -(defun org-do-latex-and-related (_limit) +(defun org-do-latex-and-related (limit) "Highlight LaTeX snippets and environments, entities and sub/superscript. Stop at first highlighted object, if any. Return t if some highlighting was done, nil otherwise." (when (org-string-nw-p org-latex-and-related-regexp) - (catch 'found - (while (re-search-forward org-latex-and-related-regexp - nil t) ;; on purpose, we ignore LIMIT - (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 - 0)) - (start (+ offset (match-beginning 0))) - (end (match-end 0))) - (if (memq 'native org-highlight-latex-and-related) - (org-src-font-lock-fontify-block "latex" start end) - (font-lock-prepend-text-property start end - 'face 'org-latex-and-related)) - (add-text-properties (+ offset (match-beginning 0)) (match-end 0) - '(font-lock-multiline t))) - (throw 'found t))) - nil))) + (let ((latex-prefix-re (rx (or "$" "\\(" "\\["))) + (blank-line-re (rx (and "\n" (zero-or-more (or " " "\t")) "\n")))) + (catch 'found + (while (and (< (point) limit) + (re-search-forward org-latex-and-related-regexp nil t)) + (cond + ((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)))) + ;; Try to limit false positives. In this case, ignore + ;; $$...$$, \(...\), and \[...\] LaTeX constructs if they + ;; contain an empty line. + ((save-excursion + (goto-char (match-beginning 0)) + (and (looking-at-p latex-prefix-re) + (save-match-data + (re-search-forward blank-line-re (1- (match-end 0)) t))))) + (t + (let* ((offset (if (memq (char-after (1+ (match-beginning 0))) + '(?_ ?^)) + 1 + 0)) + (start (+ offset (match-beginning 0))) + (end (match-end 0))) + (if (memq 'native org-highlight-latex-and-related) + (org-src-font-lock-fontify-block "latex" start end) + (font-lock-prepend-text-property start end + 'face 'org-latex-and-related)) + (add-text-properties (+ offset (match-beginning 0)) (match-end 0) + '(font-lock-multiline t)) + (throw 'found t))))) + nil)))) (defun org-restart-font-lock () "Restart `font-lock-mode', to force refontification." @@ -5636,15 +5642,22 @@ needs to be inserted at a specific position in the font-lock sequence.") (list (format org-heading-keyword-regexp-format org-todo-regexp) '(2 (org-get-todo-face 2) t)) + ;; TODO + (when org-fontify-todo-headline + (list (format org-heading-keyword-regexp-format + (concat + "\\(?:" + (mapconcat 'regexp-quote org-not-done-keywords "\\|") + "\\)")) + '(2 'org-headline-todo t))) ;; DONE - (if org-fontify-done-headline - (list (format org-heading-keyword-regexp-format - (concat - "\\(?:" - (mapconcat 'regexp-quote org-done-keywords "\\|") - "\\)")) - '(2 'org-headline-done t)) - nil) + (when org-fontify-done-headline + (list (format org-heading-keyword-regexp-format + (concat + "\\(?:" + (mapconcat 'regexp-quote org-done-keywords "\\|") + "\\)")) + '(2 'org-headline-done t))) ;; Priorities '(org-font-lock-add-priority-faces) ;; Tags @@ -5778,20 +5791,17 @@ needs to be inserted at a specific position in the font-lock sequence.") (org-font-lock-ensure) (buffer-string)))) -(defvar org-m nil) -(defvar org-l nil) -(defvar org-f nil) (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)) - (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 (unless org-level-color-stars-only org-f)))) + (let* ((org-l0 (- (match-end 2) (match-beginning 1) 1)) + (org-l (if org-odd-levels-only (1+ (/ org-l0 2)) org-l0)) + (org-f (if org-cycle-level-faces + (nth (% (1- org-l) org-n-level-faces) org-level-faces) + (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 (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. @@ -5826,11 +5836,13 @@ If TAG is a number, get the corresponding match group." 'tag 'org-tag (cdr (assoc tag org-tag-faces))) 'org-tag))) +(defvar org-priority-regexp) ; defined later in the file + (defun org-font-lock-add-priority-faces (limit) "Add the special priority faces." - (while (re-search-forward "^\\*+ .*?\\(\\[#\\(.\\)\\]\\)" limit t) + (while (re-search-forward org-priority-regexp limit t) (add-text-properties - (match-beginning 1) (match-end 1) + (match-beginning 1) (1+ (match-end 2)) (list 'face (org-get-priority-face (string-to-char (match-string 2))) 'font-lock-fontified t)))) @@ -5914,7 +5926,7 @@ and subscripts." "Remove outline overlays that do not contain non-white stuff." (dolist (o (overlays-at pos)) (and (eq 'outline (overlay-get o 'invisible)) - (not (string-match "\\S-" (buffer-substring (overlay-start o) + (not (string-match-p "\\S-" (buffer-substring (overlay-start o) (overlay-end o)))) (delete-overlay o)))) @@ -5963,21 +5975,37 @@ open and agenda-wise Org files." ;;;; Headlines visibility (defun org-show-entry () - "Show the body directly following this heading. + "Show the body directly following its heading. Show the heading too, if it is currently invisible." (interactive) (save-excursion - (ignore-errors - (org-back-to-heading t) - (org-flag-region - (line-end-position 0) - (save-excursion - (if (re-search-forward - (concat "[\r\n]\\(" org-outline-regexp "\\)") nil t) - (match-beginning 1) - (point-max))) - nil - 'outline)))) + (org-back-to-heading-or-point-min t) + (org-flag-region + (line-end-position 0) + (save-excursion + (if (re-search-forward + (concat "[\r\n]\\(" org-outline-regexp "\\)") nil t) + (match-beginning 1) + (point-max))) + nil + 'outline) + (org-cycle-hide-drawers 'children))) + +(defun org-hide-entry () + "Hide the body directly following its heading." + (interactive) + (save-excursion + (org-back-to-heading-or-point-min t) + (when (org-at-heading-p) (forward-line)) + (org-flag-region + (line-end-position 0) + (save-excursion + (if (re-search-forward + (concat "[\r\n]" org-outline-regexp) nil t) + (line-end-position 0) + (point-max))) + t + 'outline))) (defun org-show-children (&optional level) "Show all direct subheadings of this heading. @@ -5985,36 +6013,37 @@ 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") - (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. - (org-flag-heading 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)) - (org-flag-heading nil))))) + (unless (org-before-first-heading-p) + (save-excursion + (org-with-limited-levels (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. + (org-flag-heading 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)) + (org-flag-heading nil)))))) (defun org-show-subtree () "Show everything after this heading at deeper levels." @@ -6022,117 +6051,135 @@ heading to appear." (org-flag-region (point) (save-excursion (org-end-of-subtree t t)) nil 'outline)) -;;;; Blocks visibility +;;;; Blocks and drawers visibility -(defun org-hide-block-toggle-maybe () - "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) - (ignore-errors (org-hide-block-toggle))) +(defun org--hide-wrapper-toggle (element category force no-error) + "Toggle visibility for ELEMENT. -(defun org-hide-block-toggle (&optional force) +ELEMENT is a block or drawer type parsed element. CATEGORY is +either `block' or `drawer'. When FORCE is `off', show the block +or drawer. If it is non-nil, hide it unconditionally. Throw an +error when not at a block or drawer, unless NO-ERROR is non-nil. + +Return a non-nil value when toggling is successful." + (let ((type (org-element-type element))) + (cond + ((memq type + (pcase category + (`drawer '(drawer property-drawer)) + (`block '(center-block + comment-block dynamic-block example-block export-block + quote-block special-block src-block verse-block)) + (_ (error "Unknown category: %S" category)))) + (let* ((post (org-element-property :post-affiliated element)) + (start (save-excursion + (goto-char post) + (line-end-position))) + (end (save-excursion + (goto-char (org-element-property :end element)) + (skip-chars-backward " \t\n") + (line-end-position)))) + ;; Do nothing when not before or at the block opening line or + ;; at the block closing line. + (unless (let ((eol (line-end-position))) + (and (> eol start) (/= eol end))) + (let* ((spec (if (eq category 'block) 'org-hide-block 'outline)) + (flag + (cond ((eq force 'off) nil) + (force t) + ((eq spec (get-char-property start 'invisible)) nil) + (t t)))) + (org-flag-region start end flag spec)) + ;; When the block is hidden away, make sure point is left in + ;; a visible part of the buffer. + (when (invisible-p (max (1- (point)) (point-min))) + (goto-char post)) + ;; Signal success. + t))) + (no-error nil) + (t + (user-error (if (eq category 'drawer) + "Not at a drawer" + "Not at a block")))))) + +(defun org-hide-block-toggle (&optional force no-error element) "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." +a block, unless NO-ERROR is non-nil. When optional argument +ELEMENT is provided, consider it instead of the current block. + +Return a non-nil value when toggling is successful." (interactive) - (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* ((post (org-element-property :post-affiliated element)) - (start (save-excursion - (goto-char post) - (line-end-position))) - (end (save-excursion - (goto-char (org-element-property :end element)) - (skip-chars-backward " \t\n") - (line-end-position)))) - ;; Do nothing when not before or at the block opening line or at - ;; the block closing line. - (unless (let ((eol (line-end-position))) (and (> eol start) (/= eol end))) - (cond ((eq force 'off) - (org-flag-region start end nil 'org-hide-block)) - (force - (org-flag-region start end t 'org-hide-block)) - ((eq (get-char-property start 'invisible) 'org-hide-block) - (org-flag-region start end nil 'org-hide-block)) - (t - (org-flag-region start end t 'org-hide-block))) - ;; When the block is hidden away, make sure point is left in - ;; a visible part of the buffer. - (when (invisible-p (max (1- (point)) (point-min))) - (goto-char post)) - ;; Signal success. - t)))) + (org--hide-wrapper-toggle + (or element (org-element-at-point)) 'block force no-error)) -(defun org-hide-block-toggle-all () - "Toggle the visibility of all blocks in the current buffer." - (org-block-map 'org-hide-block-toggle)) +(defun org-hide-drawer-toggle (&optional force no-error element) + "Toggle the visibility of the current drawer. + +When optional argument FORCE is `off', make drawer visible. If +it is non-nil, hide it unconditionally. Throw an error when not +at a drawer, unless NO-ERROR is non-nil. When optional argument +ELEMENT is provided, consider it instead of the current drawer. + +Return a non-nil value when toggling is successful." + (interactive) + (org--hide-wrapper-toggle + (or element (org-element-at-point)) 'drawer force no-error)) (defun org-hide-block-all () "Fold all blocks in the current buffer." (interactive) (org-show-all '(blocks)) - (org-block-map 'org-hide-block-toggle-maybe)) - -;;;; Drawers visibility + (org-block-map 'org-hide-block-toggle)) -(defun org-cycle-hide-drawers (state &optional exceptions) +(defun org-hide-drawer-all () + "Fold all drawers in the current buffer." + (save-excursion + (goto-char (point-min)) + (while (re-search-forward org-drawer-regexp nil t) + (let* ((pair (get-char-property-and-overlay (line-beginning-position) + 'invisible)) + (o (cdr-safe pair))) + (if (overlayp o) (goto-char (overlay-end o)) ;invisible drawer + (pcase (get-char-property-and-overlay (point) 'invisible) + (`(outline . ,o) (goto-char (overlay-end o))) ;already folded + (_ + (let* ((drawer (org-element-at-point)) + (type (org-element-type drawer))) + (when (memq type '(drawer property-drawer)) + (org-hide-drawer-toggle t nil 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-cycle-hide-drawers (state) "Re-hide all drawers after a visibility state change. STATE should be one of the symbols listed in the docstring of -`org-cycle-hook'. When non-nil, optional argument EXCEPTIONS is -a list of strings specifying which drawers should not be hidden." +`org-cycle-hook'." (when (and (derived-mode-p 'org-mode) (not (memq state '(overview folded contents)))) - (save-excursion - (let* ((globalp (eq state 'all)) - (beg (if globalp (point-min) (point))) - (end (if globalp (point-max) - (if (eq state 'children) - (save-excursion (outline-next-heading) (point)) - (org-end-of-subtree t))))) + (let* ((global? (eq state 'all)) + (beg (if global? (point-min) (line-beginning-position))) + (end (cond (global? (point-max)) + ((eq state 'children) (org-entry-end-position)) + (t (save-excursion (org-end-of-subtree t t)))))) + (save-excursion (goto-char beg) - (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 beg end) - "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. - -When buffer positions BEG and END are provided, hide or show that -region as a drawer without further ado." - (if (and beg end) (org-flag-region beg end flag 'org-hide-drawer) - (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))) - (org-flag-region - (save-excursion (goto-char post) (line-end-position)) - (save-excursion (goto-char (org-element-property :end drawer)) - (skip-chars-backward " \t\n") - (line-end-position)) - flag 'org-hide-drawer) - ;; When the drawer is hidden away, make sure point lies in - ;; a visible part of the buffer. - (when (invisible-p (max (1- (point)) (point-min))) - (goto-char post))))))) + (while (re-search-forward org-drawer-regexp end t) + (pcase (get-char-property-and-overlay (point) 'invisible) + ;; Do not fold already folded drawers. + (`(outline . ,o) (goto-char (overlay-end o))) + (_ + (let ((drawer (org-element-at-point))) + (when (memq (org-element-type drawer) '(drawer property-drawer)) + (org-hide-drawer-toggle t nil 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))))))))))) ;;;; Visibility cycling @@ -6147,13 +6194,31 @@ By default, the function expands headings, blocks and drawers. When optional argument TYPE is a list of symbols among `blocks', `drawers' and `headings', to only expand one specific type." (interactive) - (dolist (type (or types '(blocks drawers headings))) - (org-flag-region (point-min) (point-max) nil - (pcase type - (`blocks 'org-hide-block) - (`drawers 'org-hide-drawer) - (`headings 'outline) - (_ (error "Invalid type: %S" type)))))) + (let ((types (or types '(blocks drawers headings)))) + (when (memq 'blocks types) + (org-flag-region (point-min) (point-max) nil 'org-hide-block)) + (cond + ;; Fast path. Since headings and drawers share the same + ;; invisible spec, clear everything in one go. + ((and (memq 'headings types) + (memq 'drawers types)) + (org-flag-region (point-min) (point-max) nil 'outline)) + ((memq 'headings types) + (org-flag-region (point-min) (point-max) nil 'outline) + (org-cycle-hide-drawers 'all)) + ((memq 'drawers types) + (save-excursion + (goto-char (point-min)) + (while (re-search-forward org-drawer-regexp nil t) + (let* ((pair (get-char-property-and-overlay (line-beginning-position) + 'invisible)) + (o (cdr-safe pair))) + (if (overlayp o) (goto-char (overlay-end o)) + (pcase (get-char-property-and-overlay (point) 'invisible) + (`(outline . ,o) + (goto-char (overlay-end o)) + (delete-overlay o)) + (_ nil)))))))))) ;;;###autoload (defun org-cycle (&optional arg) @@ -6204,11 +6269,11 @@ 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." +As a special case, if point is at the very beginning of the buffer, if +there is no headline there, and if the variable `org-cycle-global-at-bob' +is non-nil, this function acts as if called with prefix argument \ +\(`\\[universal-argument] TAB', +same as `S-TAB') also when called without prefix argument." (interactive "P") (org-load-modules-maybe) (unless (or (run-hook-with-args-until-success 'org-tab-first-hook) @@ -6220,63 +6285,22 @@ if the variable `org-cycle-global-at-bob' is t." (and (boundp 'org-inlinetask-min-level) org-inlinetask-min-level (1- org-inlinetask-min-level)))) - (nstars (and limit-level - (if org-odd-levels-only - (and limit-level (1- (* limit-level 2))) - limit-level))) + (nstars + (and limit-level + (if org-odd-levels-only + (1- (* 2 limit-level)) + limit-level))) (org-outline-regexp - (if (not (derived-mode-p 'org-mode)) - outline-regexp - (concat "\\*" (if nstars (format "\\{1,%d\\} " nstars) "+ ")))) - (bob-special (and org-cycle-global-at-bob (not arg) (bobp) - (not (looking-at org-outline-regexp)))) - (org-cycle-hook - (if bob-special - (delq 'org-optimize-window-after-visibility-change - (copy-sequence org-cycle-hook)) - org-cycle-hook)) - (pos (point))) - + (format "\\*%s " (if nstars (format "\\{1,%d\\}" nstars) "+")))) (cond - ((equal arg '(16)) (setq last-command 'dummy) (org-set-startup-visibility) (org-unlogged-message "Startup visibility, plus VISIBILITY properties")) - ((equal arg '(64)) (org-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" (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)) - - ;; Global cycling: delegate to `org-cycle-internal-global'. - (bob-special (org-cycle-internal-global)) - - ;; Drawers: delegate to `org-flag-drawer'. - ((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. ((integerp arg) (save-excursion @@ -6284,52 +6308,79 @@ Use `\\[org-edit-special]' to edit table.el tables")) (outline-up-heading (if (< arg 0) (- arg) (- (funcall outline-level) arg))) (org-show-subtree))) - + ;; Global cycling at BOB: delegate to `org-cycle-internal-global'. + ((and org-cycle-global-at-bob + (bobp) + (not (looking-at org-outline-regexp))) + (let ((org-cycle-hook + (remq 'org-optimize-window-after-visibility-change + org-cycle-hook))) + (org-cycle-internal-global))) + ;; Try CDLaTeX TAB completion. + ((org-try-cdlatex-tab)) ;; Inline task: delegate to `org-inlinetask-toggle-visibility'. ((and (featurep 'org-inlinetask) (org-inlinetask-at-task-p) (or (bolp) (not (eq org-cycle-emulate-tab 'exc-hl-bol)))) (org-inlinetask-toggle-visibility)) - - ;; At an item/headline: delegate to `org-cycle-internal-local'. - ((and (or (and org-cycle-include-plain-lists (org-at-item-p)) - (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)) - - ;; From there: TAB emulation and template completion. - (buffer-read-only (org-back-to-heading)) - - ((run-hook-with-args-until-success - 'org-tab-after-check-for-cycling-hook)) - - ((run-hook-with-args-until-success - 'org-tab-before-tab-emulation-hook)) - - ((and (eq org-cycle-emulate-tab 'exc-hl-bol) - (or (not (bolp)) - (not (looking-at org-outline-regexp)))) - (call-interactively (global-key-binding "\t"))) - - ((if (and (memq org-cycle-emulate-tab '(white whitestart)) - (save-excursion (beginning-of-line 1) (looking-at "[ \t]*")) - (or (and (eq org-cycle-emulate-tab 'white) - (= (match-end 0) (point-at-eol))) - (and (eq org-cycle-emulate-tab 'whitestart) - (>= (match-end 0) pos)))) - t - (eq org-cycle-emulate-tab t)) - (call-interactively (global-key-binding "\t"))) - - (t (save-excursion - (org-back-to-heading) - (org-cycle))))))) + (t + (let ((pos (point)) + (element (org-element-at-point))) + (cond + ;; Try toggling visibility for block at point. + ((org-hide-block-toggle nil t element)) + ;; Try toggling visibility for drawer at point. + ((org-hide-drawer-toggle nil t element)) + ;; Table: enter it or move to the next field. + ((and (org-match-line "[ \t]*[|+]") + (org-element-lineage element '(table) t)) + (if (and (eq 'table (org-element-type element)) + (eq 'table.el (org-element-property :type element))) + (message (substitute-command-keys "\\\ +Use `\\[org-edit-special]' to edit table.el tables")) + (org-table-justify-field-maybe) + (call-interactively #'org-table-next-field))) + ((run-hook-with-args-until-success + 'org-tab-after-check-for-table-hook)) + ;; At an item/headline: delegate to `org-cycle-internal-local'. + ((and (or (and org-cycle-include-plain-lists + (let ((item (org-element-lineage element + '(item plain-list) + t))) + (and item + (= (line-beginning-position) + (org-element-property :post-affiliated + item))))) + (org-match-line org-outline-regexp)) + (or (bolp) (not (eq org-cycle-emulate-tab 'exc-hl-bol)))) + (org-cycle-internal-local)) + ;; From there: TAB emulation and template completion. + (buffer-read-only (org-back-to-heading)) + ((run-hook-with-args-until-success + 'org-tab-after-check-for-cycling-hook)) + ((run-hook-with-args-until-success + 'org-tab-before-tab-emulation-hook)) + ((and (eq org-cycle-emulate-tab 'exc-hl-bol) + (or (not (bolp)) + (not (looking-at org-outline-regexp)))) + (call-interactively (global-key-binding (kbd "TAB")))) + ((or (eq org-cycle-emulate-tab t) + (and (memq org-cycle-emulate-tab '(white whitestart)) + (save-excursion (beginning-of-line 1) (looking-at "[ \t]*")) + (or (and (eq org-cycle-emulate-tab 'white) + (= (match-end 0) (point-at-eol))) + (and (eq org-cycle-emulate-tab 'whitestart) + (>= (match-end 0) pos))))) + (call-interactively (global-key-binding (kbd "TAB")))) + (t + (save-excursion + (org-back-to-heading) + (org-cycle)))))))))) (defun org-cycle-internal-global () "Do the global cycling action." ;; Hack to avoid display of messages for .org attachments in Gnus - (let ((ga (string-match "\\*fontification" (buffer-name)))) + (let ((ga (string-match-p "\\*fontification" (buffer-name)))) (cond ((and (eq last-command this-command) (eq org-cycle-global-status 'overview)) @@ -6377,19 +6428,23 @@ Use `\\[org-edit-special]' to edit table.el tables")) (setq has-children (org-list-has-child-p (point) struct))) (org-back-to-heading) (setq eoh (save-excursion (outline-end-of-heading) (point))) - (setq eos (save-excursion (org-end-of-subtree t t) - (when (bolp) (backward-char)) (point))) + (setq eos (save-excursion + (org-end-of-subtree t t) + (unless (eobp) (forward-char -1)) + (point))) (setq has-children - (or (save-excursion - (let ((level (funcall outline-level))) - (outline-next-heading) - (and (org-at-heading-p t) - (> (funcall outline-level) level)))) - (save-excursion - (org-list-search-forward (org-item-beginning-re) eos t))))) + (or + (save-excursion + (let ((level (funcall outline-level))) + (outline-next-heading) + (and (org-at-heading-p t) + (> (funcall outline-level) level)))) + (and (eq org-cycle-include-plain-lists 'integrate) + (save-excursion + (org-list-search-forward (org-item-beginning-re) eos t)))))) ;; Determine end invisible part of buffer (EOL) (beginning-of-line 2) - (while (and (not (eobp)) ;This is like `next-line'. + (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))) @@ -6467,18 +6522,15 @@ Use `\\[org-edit-special]' to edit table.el tables")) 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) - (org-show-all '(headings blocks)) - (outline-hide-sublevels arg) - (setq org-cycle-global-status 'contents)) - ((equal arg '(4)) - (org-set-startup-visibility) - (org-unlogged-message "Startup visibility, plus VISIBILITY properties.")) - (t - (org-cycle '(4)))))) + (cond + ((integerp arg) + (org-content arg) + (setq org-cycle-global-status 'contents)) + ((equal arg '(4)) + (org-set-startup-visibility) + (org-unlogged-message "Startup visibility, plus VISIBILITY properties.")) + (t + (org-cycle '(4))))) (defun org-set-startup-visibility () "Set the visibility required by startup options and properties." @@ -6526,51 +6578,60 @@ With a numeric prefix, show all headlines up to that level." (org-end-of-subtree))))))) (defun org-overview () - "Switch to overview mode, showing only top-level headlines. -This shows all headlines with a level equal or greater than the level -of the first headline in the buffer. This is important, because if the -first headline is not level one, then (hide-sublevels 1) gives confusing -results." + "Switch to overview mode, showing only top-level headlines." (interactive) + (org-show-all '(headings drawers)) (save-excursion - (let ((level - (save-excursion - (goto-char (point-min)) - (when (re-search-forward org-outline-regexp-bol nil t) - (goto-char (match-beginning 0)) - (funcall outline-level))))) - (and level (outline-hide-sublevels level))))) + (goto-char (point-min)) + (when (re-search-forward org-outline-regexp-bol nil t) + (let* ((last (line-end-position)) + (level (- (match-end 0) (match-beginning 0) 1)) + (regexp (format "^\\*\\{1,%d\\} " level))) + (while (re-search-forward regexp nil :move) + (org-flag-region last (line-end-position 0) t 'outline) + (setq last (line-end-position)) + (setq level (- (match-end 0) (match-beginning 0) 1)) + (setq regexp (format "^\\*\\{1,%d\\} " level))) + (org-flag-region last (point) t 'outline))))) (defun org-content (&optional arg) "Show all headlines in the buffer, like a table of contents. With numerical argument N, show content up to level N." - (interactive "P") - (org-overview) + (interactive "p") + (org-show-all '(headings drawers)) (save-excursion - ;; Visit all headings and show their offspring - (and (integerp arg) (org-overview)) (goto-char (point-max)) - (catch 'exit - (while (and (progn (condition-case nil - (outline-previous-visible-heading 1) - (error (goto-char (point-min)))) - t) - (looking-at org-outline-regexp)) - (if (integerp arg) - (org-show-children (1- arg)) - (outline-show-branches)) - (when (bobp) (throw 'exit nil)))))) - + (let ((regexp (if (and (wholenump arg) (> arg 0)) + (format "^\\*\\{1,%d\\} " arg) + "^\\*+ ")) + (last (point))) + (while (re-search-backward regexp nil t) + (org-flag-region (line-end-position) last t 'outline) + (setq last (line-end-position 0)))))) + +(defvar org-scroll-position-to-restore nil + "Temporarily store scroll position to restore.") (defun org-optimize-window-after-visibility-change (state) "Adjust the window after a change in outline visibility. This function is the default value of the hook `org-cycle-hook'." (when (get-buffer-window (current-buffer)) - (cond - ((eq state 'content) nil) - ((eq state 'all) nil) - ((eq state 'folded) nil) - ((eq state 'children) (or (org-subtree-end-visible-p) (recenter 1))) - ((eq state 'subtree) (or (org-subtree-end-visible-p) (recenter 1)))))) + (let ((repeat (eq last-command this-command))) + (unless repeat + (setq org-scroll-position-to-restore nil)) + (cond + ((eq state 'content) nil) + ((eq state 'all) nil) + ((and org-scroll-position-to-restore repeat + (eq state 'folded)) + (set-window-start nil org-scroll-position-to-restore)) + ((eq state 'folded) nil) + ((eq state 'children) + (setq org-scroll-position-to-restore (window-start)) + (or (org-subtree-end-visible-p) (recenter 1))) + ((eq state 'subtree) + (unless repeat + (setq org-scroll-position-to-restore (window-start))) + (or (org-subtree-end-visible-p) (recenter 1))))))) (defun org-clean-visibility-after-subtree-move () "Fix visibility issues after moving a subtree." @@ -6671,8 +6732,7 @@ information." ;; 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 org-hide-drawer outline)) + (when (memq (overlay-get o 'invisible) '(org-hide-block outline)) (delete-overlay o))) (unless (org-before-first-heading-p) (org-with-limited-levels @@ -6785,7 +6845,7 @@ frame is not changed." (pop-to-buffer ibuf)) (t (error "Invalid value"))) (narrow-to-region beg end) - (org-show-all '(headings blocks)) + (org-show-all '(headings drawers blocks)) (goto-char pos) (run-hook-with-args 'org-cycle-hook 'all) (and (window-live-p cwin) (select-window cwin)))) @@ -6812,27 +6872,6 @@ frame is not changed." ;;; Inserting headlines -(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 n) t) - (save-match-data - (looking-at "[ \t]*$"))))) - -(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)) - -(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)) - (defun org--blank-before-heading-p (&optional parent) "Non-nil when an empty line should precede a new heading here. When optional argument PARENT is non-nil, consider parent @@ -7344,9 +7383,17 @@ Assume point is at a heading or an inlinetask beginning." (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)))) + (org-indent-region (match-beginning 0) (match-end 0))) + (when (looking-at org-logbook-drawer-re) + (let ((end-marker (move-marker (make-marker) (match-end 0))) + (col (+ (current-indentation) diff))) + (when (wholenump col) + (while (< (point) end-marker) + (indent-line-to col) + (forward-line))))) (catch 'no-shift - (when (zerop diff) (throw 'no-shift nil)) + (when (or (zerop diff) (not (eq org-adapt-indentation t))) + (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. @@ -7761,8 +7808,9 @@ If yes, remember the marker and the distance to BEG." "Narrow to the subtree at point or widen a narrowed buffer." (interactive) (if (buffer-narrowed-p) - (widen) - (org-narrow-to-subtree))) + (progn (widen) (message "Buffer widen")) + (org-narrow-to-subtree) + (message "Buffer narrowed to current subtree"))) (defun org-narrow-to-block () "Narrow buffer to the current block." @@ -7843,7 +7891,8 @@ with the original repeater." (nmin 1) (nmax n) (n-no-remove -1) - (idprop (org-entry-get nil "ID"))) + (org-id-overriding-file-name (buffer-file-name (buffer-base-buffer))) + (idprop (org-entry-get beg "ID"))) (when (and doshift (string-match-p "<[^<>\n]+ [.+]?\\+[0-9]+[hdwmy][^<>\n]*>" template)) @@ -7885,6 +7934,131 @@ with the original repeater." (buffer-string))))) (goto-char beg))) +;;; Outline path + +(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--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. +WIDTH is the maximum number of characters that is available. +PREFIX is a prefix to be included in the returned string, +such as the file name. +SEPARATOR is inserted between the different parts of the path, +the default is \"/\"." + (setq width (or width 79)) + (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. + +If FILE is non-nil, prepend the output with the file name. +If CURRENT is non-nil, append the current heading to the output. +SEPARATOR is passed through to `org-format-outline-path'. It separates +the different parts of the path and defaults to \"/\". +If JUST-RETURN-STRING is non-nil, return a string, don't display a message." + (interactive "P") + (let* (case-fold-search + (bfn (buffer-file-name (buffer-base-buffer))) + (path (and (derived-mode-p 'org-mode) (org-get-outline-path))) + res) + (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 + path + (1- (frame-width)) + (and file bfn (concat (file-name-nondirectory bfn) separator)) + separator)) + (add-face-text-property 0 (length res) + `(:height ,(face-attribute 'default :height)) + nil res) + (if just-return-string + res + (org-unlogged-message "%s" res)))) + ;;; Outline Sorting (defun org-sort (&optional with-case) @@ -7907,8 +8081,6 @@ Optional argument WITH-CASE means sort case-sensitively." (org-link-display-format s) t t) t t)) -(defvar org-priority-regexp) ; defined later in the file - (defvar org-after-sorting-entries-or-items-hook nil "Hook that is run after a bunch of entries or items have been sorted. When children are sorted, the cursor is in the parent line when this @@ -8002,7 +8174,7 @@ function is being called interactively." (setq end (point-max)) (setq what "top-level") (goto-char start) - (org-show-all '(headings blocks)))) + (org-show-all '(headings drawers blocks)))) (setq beg (point)) (when (>= beg end) (goto-char start) (user-error "Nothing to sort")) @@ -8112,7 +8284,7 @@ function is being called interactively." ((= dcst ?p) (if (re-search-forward org-priority-regexp (point-at-eol) t) (string-to-char (match-string 2)) - org-default-priority)) + org-priority-default)) ((= dcst ?r) (or (org-entry-get nil property) "")) ((= dcst ?o) @@ -8269,13 +8441,14 @@ the value of the drawer property." (inhibit-read-only t) (inherit? (org-property-inherit-p dprop)) (property-re (org-re-property (concat (regexp-quote dprop) "\\+?") t)) - (global (and inherit? (org--property-global-value dprop nil)))) + (global-or-keyword (and inherit? + (org--property-global-or-keyword-value dprop nil)))) (with-silent-modifications (org-with-point-at 1 - ;; Set global values (e.g., values defined through - ;; "#+PROPERTY:" keywords) to the whole buffer. - (when global (put-text-property (point-min) (point-max) tprop global)) - ;; Set local values. + ;; Set global and keyword based values to the whole buffer. + (when global-or-keyword + (put-text-property (point-min) (point-max) tprop global-or-keyword)) + ;; Set values based on property-drawers throughout the document. (while (re-search-forward property-re nil t) (when (org-at-property-p) (org-refresh-property tprop (org-entry-get (point) dprop) inherit?)) @@ -8283,21 +8456,30 @@ the value of the drawer property." (defun org-refresh-property (tprop p &optional inherit) "Refresh the buffer text property TPROP from the drawer property P. -The refresh happens only for the current headline, or the whole -sub-tree if optional argument INHERIT is non-nil." - (unless (org-before-first-heading-p) - (save-excursion - (org-back-to-heading t) - (let ((start (point)) - (end (save-excursion - (if inherit (org-end-of-subtree t t) - (or (outline-next-heading) (point-max)))))) - (if (symbolp tprop) - ;; TPROP is a text property symbol. - (put-text-property start end tprop p) - ;; TPROP is an alist with (property . function) elements. - (pcase-dolist (`(,prop . ,f) tprop) - (put-text-property start end prop (funcall f p)))))))) + +The refresh happens only for the current entry, or the whole +sub-tree if optional argument INHERIT is non-nil. + +If point is before first headline, the function applies to the +part before the first headline. In that particular case, when +optional argument INHERIT is non-nil, it refreshes properties for +the whole buffer." + (save-excursion + (org-back-to-heading-or-point-min t) + (let ((start (point)) + (end (save-excursion + (cond ((and inherit (org-before-first-heading-p)) + (point-max)) + (inherit + (org-end-of-subtree t t)) + ((outline-next-heading)) + ((point-max)))))) + (if (symbolp tprop) + ;; TPROP is a text property symbol. + (put-text-property start end tprop p) + ;; TPROP is an alist with (property . function) elements. + (pcase-dolist (`(,prop . ,f) tprop) + (put-text-property start end prop (funcall f p))))))) (defun org-refresh-category-properties () "Refresh category text properties in the buffer." @@ -8313,9 +8495,9 @@ sub-tree if optional argument INHERIT is non-nil." (t org-category)))) (with-silent-modifications (org-with-wide-buffer - ;; Set buffer-wide category. Search last #+CATEGORY keyword. - ;; This is the default category for the buffer. If none is - ;; found, fall-back to `org-category' or buffer file name. + ;; Set buffer-wide property from keyword. Search last #+CATEGORY + ;; keyword. If none is found, fall-back to `org-category' or + ;; buffer file name, or set it by the document property drawer. (put-text-property (point-min) (point-max) 'org-category @@ -8327,15 +8509,20 @@ sub-tree if optional argument INHERIT is non-nil." (throw 'buffer-category (org-element-property :value element))))) default-category)) - ;; Set sub-tree specific categories. + ;; Set categories from the document property drawer or + ;; property drawers in the outline. If category is found in + ;; the property drawer for the whole buffer that value + ;; overrides the keyword-based value set above. (goto-char (point-min)) (let ((regexp (org-re-property "CATEGORY"))) (while (re-search-forward regexp nil t) (let ((value (match-string-no-properties 3))) (when (org-at-property-p) (put-text-property - (save-excursion (org-back-to-heading t) (point)) - (save-excursion (org-end-of-subtree t t) (point)) + (save-excursion (org-back-to-heading-or-point-min t)) + (save-excursion (if (org-before-first-heading-p) + (point-max) + (org-end-of-subtree t t))) 'org-category value))))))))) @@ -8659,31 +8846,30 @@ a link." ;; a link, a footnote reference. ((memq type '(headline inlinetask)) (org-match-line org-complex-heading-regexp) - (if (and (match-beginning 5) - (>= (point) (match-beginning 5)) - (< (point) (match-end 5))) - ;; On tags. - (org-tags-view - arg - (save-excursion - (let* ((beg (match-beginning 5)) - (end (match-end 5)) - (beg-tag (or (search-backward ":" beg 'at-limit) (point))) - (end-tag (search-forward ":" end nil 2))) - (buffer-substring (1+ beg-tag) (1- end-tag))))) - ;; Not on tags. - (pcase (org-offer-links-in-entry (current-buffer) (point) arg) - (`(nil . ,_) - (require 'org-attach) - (message "Opening attachment-dir") - (if (equal arg '(4)) - (org-attach-reveal-in-emacs) - (org-attach-reveal))) - (`(,links . ,links-end) - (dolist (link (if (stringp links) (list links) links)) - (search-forward link nil links-end) - (goto-char (match-beginning 0)) - (org-open-at-point arg)))))) + (let ((tags-beg (match-beginning 5)) + (tags-end (match-end 5))) + (if (and tags-beg (>= (point) tags-beg) (< (point) tags-end)) + ;; On tags. + (org-tags-view + arg + (save-excursion + (let* ((beg-tag (or (search-backward ":" tags-beg 'at-limit) (point))) + (end-tag (search-forward ":" tags-end nil 2))) + (buffer-substring (1+ beg-tag) (1- end-tag))))) + ;; Not on tags. + (pcase (org-offer-links-in-entry (current-buffer) (point) arg) + (`(nil . ,_) + (require 'org-attach) + (when (org-attach-dir) + (message "Opening attachment") + (if (equal arg '(4)) + (org-attach-reveal-in-emacs) + (org-attach-reveal)))) + (`(,links . ,links-end) + (dolist (link (if (stringp links) (list links) links)) + (search-forward link nil links-end) + (goto-char (match-beginning 0)) + (org-open-at-point arg))))))) ;; On a footnote reference or at definition's label. ((or (eq type 'footnote-reference) (and (eq type 'footnote-definition) @@ -8903,639 +9089,10 @@ or to another Org file, automatically push the old position onto the ring." (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'.") - (defvar org-agenda-new-buffers nil "Buffers created to visit agenda files.") -(defvar org-refile-cache nil - "Cache for refile targets.") - -(defvar org-refile-markers nil - "All the markers used for caching refile locations.") - -(defun org-refile-marker (pos) - "Get a new refile marker, but only if caching is in use." - (if (not org-refile-use-cache) - pos - (let ((m (make-marker))) - (move-marker m pos) - (push m org-refile-markers) - m))) - -(defun org-refile-cache-clear () - "Clear the refile cache and disable all the 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")) - -(defun org-refile-cache-check-set (set) - "Check if all the markers in the cache still have live buffers." - (let (marker) - (catch 'exit - (while (and set (setq marker (nth 3 (pop set)))) - ;; If `org-refile-use-outline-path' is 'file, marker may be nil - (when (and marker (null (marker-buffer marker))) - (message "Please regenerate the refile cache with `C-0 C-c C-w'") - (sit-for 3) - (throw 'exit nil))) - t))) - -(defun org-refile-cache-put (set &rest identifiers) - "Push the refile targets SET into the cache, under IDENTIFIERS." - (let* ((key (sha1 (prin1-to-string identifiers))) - (entry (assoc key org-refile-cache))) - (if entry - (setcdr entry set) - (push (cons key set) org-refile-cache)))) - -(defun org-refile-cache-get (&rest identifiers) - "Retrieve the cached value for refile targets given by IDENTIFIERS." - (cond - ((not org-refile-cache) nil) - ((not org-refile-use-cache) (org-refile-cache-clear) nil) - (t - (let ((set (cdr (assoc (sha1 (prin1-to-string identifiers)) - org-refile-cache)))) - (and set (org-refile-cache-check-set set) set))))) - -(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 files desc descre) - (message "Getting targets...") - (with-current-buffer (or default-buffer (current-buffer)) - (dolist (entry entries) - (setq files (car entry) desc (cdr entry)) - (cond - ((null files) (setq files (list (current-buffer)))) - ((eq files 'org-agenda-files) - (setq files (org-agenda-files 'unrestricted))) - ((and (symbolp files) (fboundp files)) - (setq files (funcall files))) - ((and (symbolp files) (boundp files)) - (setq files (symbol-value files)))) - (when (stringp files) (setq files (list files))) - (cond - ((eq (car desc) :tag) - (setq descre (concat "^\\*+[ \t]+.*?:" (regexp-quote (cdr desc)) ":"))) - ((eq (car desc) :todo) - (setq descre (concat "^\\*+[ \t]+" (regexp-quote (cdr desc)) "[ \t]"))) - ((eq (car desc) :regexp) - (setq descre (cdr desc))) - ((eq (car desc) :level) - (setq descre (concat "^\\*\\{" (number-to-string - (if org-odd-levels-only - (1- (* 2 (cdr desc))) - (cdr desc))) - "\\}[ \t]"))) - ((eq (car desc) :maxlevel) - (setq descre (concat "^\\*\\{1," (number-to-string - (if org-odd-levels-only - (1- (* 2 (cdr desc))) - (cdr desc))) - "\\}[ \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)) - (or - (setq tgs (org-refile-cache-get (buffer-file-name) descre)) - (progn - (when (bufferp f) - (setq f (buffer-file-name (buffer-base-buffer f)))) - (setq f (and f (expand-file-name f))) - (when (eq org-refile-use-outline-path 'file) - (push (list (file-name-nondirectory f) f nil nil) tgs)) - (when (eq org-refile-use-outline-path 'buffer-name) - (push (list (buffer-name (buffer-base-buffer)) f nil nil) tgs)) - (when (eq org-refile-use-outline-path 'full-file-path) - (push (list (file-truename (buffer-file-name (buffer-base-buffer))) 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 - #'identity - (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)))) - (`buffer-name - (list (buffer-name - (buffer-base-buffer)))) - (_ nil)) - (mapcar (lambda (s) (replace-regexp-in-string - "/" "\\/" s nil t)) - (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") - (delete-dups (nreverse targets)))) - -(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. -WIDTH is the maximum number of characters that is available. -PREFIX is a prefix to be included in the returned string, -such as the file name. -SEPARATOR is inserted between the different parts of the path, -the default is \"/\"." - (setq width (or width 79)) - (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. - -If FILE is non-nil, prepend the output with the file name. -If CURRENT is non-nil, append the current heading to the output. -SEPARATOR is passed through to `org-format-outline-path'. It separates -the different parts of the path and defaults to \"/\". -If JUST-RETURN-STRING is non-nil, return a string, don't display a message." - (interactive "P") - (let* (case-fold-search - (bfn (buffer-file-name (buffer-base-buffer))) - (path (and (derived-mode-p 'org-mode) (org-get-outline-path))) - res) - (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 - path - (1- (frame-width)) - (and file bfn (concat (file-name-nondirectory bfn) separator)) - separator)) - (if just-return-string - (org-no-properties res) - (org-unlogged-message "%s" res)))) - -(defvar org-refile-history nil - "History for refiling operations.") - -(defvar org-after-refile-insert-hook nil - "Hook run after `org-refile' has inserted its stuff at the new location. -Note that this is still *before* the stuff will be removed from -the *old* location.") - -(defvar org-capture-last-stored-marker) -(defvar org-refile-keep nil - "Non-nil means `org-refile' will copy instead of refile.") - -(defun org-copy () - "Like `org-refile', but copy." - (interactive) - (let ((org-refile-keep t)) - (org-refile nil nil nil "Copy"))) - -(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. - -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 a `\\[universal-argument]' ARG, the command will only visit the target \ -location -and not actually move anything. - -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. - -With a numeric prefix argument of `3', emulate `org-refile-keep' -being set to t and copy to the target location, don't move it. -Beware that keeping refiled entries may result in duplicated ID -properties. - -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'. - -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 arg '(0 (64))) - (org-refile-cache-clear) - (let* ((actionmsg (cond (msg msg) - ((equal arg 3) "Refile (and keep)") - (t "Refile"))) - (regionp (org-region-active-p)) - (region-start (and regionp (region-beginning))) - (region-end (and regionp (region-end))) - (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) - (beginning-of-line) - (setq region-start (point)) - (unless (or (org-kill-is-subtree-p - (buffer-substring region-start region-end)) - (prog1 org-refile-active-region-within-subtree - (let ((s (point-at-eol))) - (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 arg '(16)) - (org-refile-goto-last-stored) - (when (or - (and (equal arg 2) - org-clock-hd-marker (marker-buffer org-clock-hd-marker) - (prog1 - (setq it (list (or org-clock-heading "running clock") - (buffer-file-name - (marker-buffer org-clock-hd-marker)) - "" - (marker-position org-clock-hd-marker))) - (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-link-bracket-re - "\\2" - (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) - pos (nth 3 it)) - (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 arg (not (equal arg 3))) - (progn - (pop-to-buffer-same-window nbuf) - (goto-char (cond (pos) - ((org-notes-order-reversed-p) (point-min)) - (t (point-max)))) - (org-show-context 'org-goto)) - (if regionp - (progn - (org-kill-new (buffer-substring region-start region-end)) - (org-save-markers-in-region region-start region-end)) - (org-copy-subtree 1 nil t)) - (with-current-buffer (setq nbuf (or (find-buffer-visiting file) - (find-file-noselect file))) - (setq reversed (org-notes-order-reversed-p)) - (org-with-wide-buffer - (if pos - (progn - (goto-char pos) - (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) - ;; Record information, according to `org-log-refile'. - ;; Do not prompt for a note when refiling multiple - ;; headlines, however. Simply add a time stamp. - (cond - ((not org-log-refile)) - (regionp - (org-map-region - (lambda () (org-add-log-setup 'refile nil nil 'time)) - (point) - (+ (point) (- region-end region-start)))) - (t - (org-add-log-setup 'refile nil nil org-log-refile))) - (and org-auto-align-tags - (let ((org-loop-over-headlines-in-active-region nil)) - (org-align-tags))) - (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))) - (org-preserve-local-variables - (delete-region - (and (org-back-to-heading t) (point)) - (min (1+ (buffer-size)) (org-end-of-subtree t t) (point)))))) - (when (featurep 'org-inlinetask) - (org-inlinetask-remove-END-maybe)) - (setq org-markers-to-move nil) - (message "%s to \"%s\" in file %s: done" actionmsg - (car it) file))))))) - -(defun org-refile-goto-last-stored () - "Go to the location where the last refile was stored." - (interactive) - (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) - "When user refile to REFLOC, find the associated target in TBL. -Also check `org-refile-target-table'." - (car (delq - nil - (mapcar - (lambda (r) (or (assoc r tbl) - (assoc r 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) - "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." - (let ((org-refile-targets org-refile-targets) - (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)) - (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 - #'completing-read)) - (extra (if org-refile-use-outline-path "/" "")) - (cbnex (concat (buffer-name) extra)) - (filename (and cfn (expand-file-name cfn))) - (tbl (mapcar - (lambda (x) - (if (and (not (member org-refile-use-outline-path - '(file full-file-path))) - (not (equal filename (nth 1 x)))) - (cons (concat (car x) extra " (" - (file-name-nondirectory (nth 1 x)) ")") - (cdr x)) - (cons (concat (car x) extra) (cdr x)))) - org-refile-target-table)) - (completion-ignore-case t) - cdef - (prompt (concat prompt - (or (and (car org-refile-history) - (concat " (default " (car org-refile-history) ")")) - (and (assoc cbnex tbl) (setq cdef cbnex) - (concat " (default " cbnex ")"))) ": ")) - pa answ parent-target child parent old-hist) - (setq old-hist org-refile-history) - (setq answ (funcall cfunc prompt tbl nil (not new-nodes) - nil 'org-refile-history (or cdef (car org-refile-history)))) - (if (setq pa (org-refile--get-location answ tbl)) - (progn - (org-refile-check-position pa) - (when (or (not org-refile-history) - (not (eq old-hist org-refile-history)) - (not (equal (car pa) (car org-refile-history)))) - (setq org-refile-history - (cons (car pa) (if (assoc (car org-refile-history) tbl) - org-refile-history - (cdr org-refile-history)))) - (when (equal (car org-refile-history) (nth 1 org-refile-history)) - (pop org-refile-history))) - pa) - (if (string-match "\\`\\(.*\\)/\\([^/]+\\)\\'" answ) - (progn - (setq parent (match-string 1 answ) - child (match-string 2 answ)) - (setq parent-target (org-refile--get-location parent tbl)) - (when (and parent-target - (or (eq new-nodes t) - (and (eq new-nodes 'confirm) - (y-or-n-p (format "Create new node \"%s\"? " - child))))) - (org-refile-new-child parent-target child))) - (user-error "Invalid target location"))))) - (declare-function org-string-nw-p "org-macs" (s)) -(defun org-refile-check-position (refile-pointer) - "Check if the refile pointer matches the headline to which it points." - (let* ((file (nth 1 refile-pointer)) - (re (nth 2 refile-pointer)) - (pos (nth 3 refile-pointer)) - buffer) - (if (and (not (markerp pos)) (not file)) - (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 - (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." - (unless parent-target - (error "Cannot find parent for new node")) - (let ((file (nth 1 parent-target)) - (pos (nth 3 parent-target)) - level) - (with-current-buffer (or (find-buffer-visiting file) - (find-file-noselect file)) - (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)) - (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 (defun org-find-dblock (name) @@ -9631,15 +9188,18 @@ block of such type." (`nil (push (cons type func) org-dynamic-block-alist)) (def (setcdr def func)))) -(defun org-dynamic-block-insert-dblock (type) +(defun org-dynamic-block-insert-dblock (type &optional interactive-p) "Insert a dynamic block of type TYPE. When used interactively, select the dynamic block types among -defined types, per `org-dynamic-block-define'." +defined types, per `org-dynamic-block-define'. If INTERACTIVE-P +is non-nil, call the dynamic block function interactively." (interactive (list (completing-read "Dynamic block: " - (org-dynamic-block-types)))) + (org-dynamic-block-types)) + t)) (pcase (org-dynamic-block-function type) (`nil (error "No such dynamic block: %S" type)) - ((and f (pred functionp)) (funcall f)) + ((and f (pred functionp)) + (if interactive-p (call-interactively f) (funcall f))) (_ (error "Invalid function for dynamic block %S" type)))) (defun org-dblock-update (&optional arg) @@ -9763,8 +9323,7 @@ block can be inserted by pressing TAB after the string \" (upcase new) org-lowest-priority)) - (user-error "Priority must be between `%c' and `%c'" - org-highest-priority org-lowest-priority)))) + ((or (< (upcase new) org-priority-highest) (> (upcase new) org-priority-lowest)) + (user-error + (if nump + "Priority must be between `%s' and `%s'" + "Priority must be between `%c' and `%c'") + org-priority-highest org-priority-lowest)))) ((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 + org-priority-lowest ; wrap around empty to lowest ;; default (if org-priority-start-cycle-with-default - org-default-priority - (1- org-default-priority)))))) + org-priority-default + (1- org-priority-default)))))) ((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 + org-priority-highest ; wrap around empty to highest ;; default (if org-priority-start-cycle-with-default - org-default-priority - (1+ org-default-priority)))))) + org-priority-default + (1+ org-priority-default)))))) (t (user-error "Invalid action"))) - (when (or (< (upcase new) org-highest-priority) - (> (upcase new) org-lowest-priority)) + (when (or (< (upcase new) org-priority-highest) + (> (upcase new) org-priority-lowest)) (if (and (memq action '(up down)) (not have) (not (eq last-command this-command))) ;; `new' is from default priority (error - "The default can not be set, see `org-default-priority' why") + "The default can not be set, see `org-priority-default' why") ;; normal cycling: `new' is beyond highest/lowest priority ;; and is wrapped around to the empty priority (setq remove t))) - (setq news (format "%c" new)) + ;; Numerical priorities are limited to 64, beyond that number, + ;; assume the priority cookie is a character. + (setq news (if (> new 64) (format "%c" new) (format "%s" new))) (if have (if remove (replace-match "" t t nil 1) @@ -11660,7 +11250,8 @@ or a character." (message "Priority removed") (message "Priority of current item set to %s" news))))) -(defun org-show-priority () +(defalias 'org-show-priority 'org-priority-show) +(defun org-priority-show () "Show the priority of the current item. This priority is composed of the main priority given with the [#A] cookies, and by additional input from the age of a schedules or deadline entry." @@ -11675,14 +11266,18 @@ and by additional input from the age of a schedules or deadline entry." (message "Priority is %d" (if pri pri -1000)))) (defun org-get-priority (s) - "Find priority cookie and return priority." + "Find priority cookie and return priority. +S is a string against which you can match `org-priority-regexp'. +If `org-priority-get-priority-function' is set to a custom +function, use it. Otherwise process S and output the priority +value, an integer." (save-match-data - (if (functionp org-get-priority-function) - (funcall org-get-priority-function s) + (if (functionp org-priority-get-priority-function) + (funcall org-priority-get-priority-function s) (if (not (string-match org-priority-regexp s)) - (* 1000 (- org-lowest-priority org-default-priority)) - (* 1000 (- org-lowest-priority - (string-to-char (match-string 2 s)))))))) + (* 1000 (- org-priority-lowest org-priority-default)) + (* 1000 (- org-priority-lowest + (org-priority-to-value (match-string 2 s)))))))) ;;;; Tags @@ -11907,7 +11502,7 @@ are also TODO tasks." (interactive "P") (org-agenda-prepare-buffers (list (current-buffer))) (let ((org--matcher-tags-todo-only todo-only)) - (org-scan-tags 'sparse-tree (cdr (org-make-tags-matcher match)) + (org-scan-tags 'sparse-tree (cdr (org-make-tags-matcher match t)) org--matcher-tags-todo-only))) (defalias 'org-tags-sparse-tree 'org-match-sparse-tree) @@ -11948,7 +11543,7 @@ instead of the agenda files." (if (car-safe files) files (org-agenda-files)))))))) -(defun org-make-tags-matcher (match) +(defun org-make-tags-matcher (match &optional only-local-tags) "Create the TAGS/TODO matcher form for the selection string MATCH. Returns a cons of the selection string MATCH and a function @@ -11966,6 +11561,9 @@ 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. +When ONLY-LOCAL-TAGS is non-nil, ignore the global tag completion +table, only get buffer tags. + See also `org-scan-tags'." (unless match ;; Get a new match request, with completion against the global @@ -11973,7 +11571,8 @@ See also `org-scan-tags'." (let ((org-last-tags-completion-table (org--tag-add-to-alist (org-get-buffer-tags) - (org-global-tags-completion-table)))) + (unless only-local-tags + (org-global-tags-completion-table))))) (setq match (completing-read "Match: " @@ -12100,7 +11699,7 @@ See also `org-scan-tags'." (cons match0 `(lambda (todo tags-list level) ,matcher))))) (defun org--tags-expand-group (group tag-groups expanded) - "Recursively Expand all tags in GROUP, according to TAG-GROUPS. + "Recursively expand all tags in GROUP, according to TAG-GROUPS. TAG-GROUPS is the list of groups used for expansion. EXPANDED is an accumulator used in recursive calls." (dolist (tag group) @@ -12148,7 +11747,9 @@ When DOWNCASED is non-nil, expand downcased TAGS." (if (not downcased) g (mapcar (lambda (s) (mapcar #'downcase s)) g))))) (cond - (single-as-list (org--tags-expand-group (list match) tag-groups nil)) + (single-as-list (org--tags-expand-group + (list (if downcased (downcase match) match)) + tag-groups nil)) (org-group-tags (let* ((case-fold-search t) (tag-syntax org-mode-syntax-table) @@ -12331,7 +11932,12 @@ in Lisp code use `org-set-tags' instead." #'org-tags-completion-function nil nil (org-make-tag-string current-tags) 'org-tags-history))))))) - (org-set-tags tags))))))) + (org-set-tags tags))))) + ;; `save-excursion' may not replace the point at the right + ;; position. + (when (and (save-excursion (skip-chars-backward "*") (bolp)) + (looking-at-p " ")) + (forward-char)))) (defun org-align-tags (&optional all) "Align tags in current entry. @@ -12712,7 +12318,8 @@ According to `org-use-tag-inheritance', tags may be inherited from parent headlines, and from the whole document, through `org-file-tags'. In this case, the returned list of tags contains tags in this order: file tags, tags inherited from -parent headlines, local tags. +parent headlines, local tags. If a tag appears multiple times, +only the most local tag is returned. However, when optional argument LOCAL is non-nil, only return tags specified at the headline. @@ -12728,12 +12335,13 @@ Inherited tags have the `inherited' text property." (let ((ltags (org--get-local-tags)) itags) (if (or local (not org-use-tag-inheritance)) ltags (while (org-up-heading-safe) - (setq itags (append (mapcar #'org-add-prop-inherited - (org--get-local-tags)) - itags))) + (setq itags (nconc (mapcar #'org-add-prop-inherited + (org--get-local-tags)) + itags))) (setq itags (append org-file-tags itags)) - (delete-dups - (append (org-remove-uninherited-tags itags) ltags)))))))) + (nreverse + (delete-dups + (nreverse (nconc (org-remove-uninherited-tags itags) ltags)))))))))) (defun org-get-buffer-tags () "Get a table of all tags used in the buffer, for completion." @@ -12921,30 +12529,52 @@ Modifications are made by side-effect. Return new alist." (defun org-get-property-block (&optional beg force) "Return the (beg . end) range of the body of the property drawer. -BEG is the beginning of the current subtree, or of the part -before the first headline. If it is not given, it will be found. -If the drawer does not exist, create it if FORCE is non-nil, or -return nil." +BEG is the beginning of the current subtree or the beginning of +the document if before the first headline. If it is not given, +it will be found. If the drawer does not exist, create it if +FORCE is non-nil, or return nil." (org-with-wide-buffer - (when beg (goto-char beg)) - (unless (org-before-first-heading-p) - (let ((beg (cond (beg) + (let ((beg (cond (beg (goto-char beg)) ((or (not (featurep 'org-inlinetask)) (org-inlinetask-in-task-p)) - (org-back-to-heading t)) - (t (org-with-limited-levels (org-back-to-heading t)))))) - (forward-line) - (when (looking-at-p org-planning-line-re) (forward-line)) - (cond ((looking-at org-property-drawer-re) - (forward-line) - (cons (point) (progn (goto-char (match-end 0)) - (line-beginning-position)))) - (force - (goto-char beg) - (org-insert-property-drawer) - (let ((pos (save-excursion (search-forward ":END:") - (line-beginning-position)))) - (cons pos pos)))))))) + (org-back-to-heading-or-point-min t) (point)) + (t (org-with-limited-levels + (org-back-to-heading-or-point-min t)) + (point))))) + ;; Move point to its position according to its positional rules. + (cond ((org-before-first-heading-p) + (while (and (org-at-comment-p) (bolp)) (forward-line))) + (t (forward-line) + (when (looking-at-p org-planning-line-re) (forward-line)))) + (cond ((looking-at org-property-drawer-re) + (forward-line) + (cons (point) (progn (goto-char (match-end 0)) + (line-beginning-position)))) + (force + (goto-char beg) + (org-insert-property-drawer) + (let ((pos (save-excursion (re-search-forward org-property-drawer-re) + (line-beginning-position)))) + (cons pos pos))))))) + +(defun org-at-property-drawer-p () + "Non-nil when point is at the first line of a property drawer." + (org-with-wide-buffer + (beginning-of-line) + (and (looking-at org-property-drawer-re) + (or (bobp) + (progn + (forward-line -1) + (cond ((org-at-heading-p)) + ((looking-at org-planning-line-re) + (forward-line -1) + (org-at-heading-p)) + ((looking-at org-comment-regexp) + (forward-line -1) + (while (and (not (bobp)) (looking-at org-comment-regexp)) + (forward-line -1)) + (looking-at org-comment-regexp)) + (t nil))))))) (defun org-at-property-p () "Non-nil when point is inside a property drawer. @@ -13000,6 +12630,10 @@ variables is set." (not (get-text-property 0 'org-unrestricted (caar allowed)))))) (completing-read "Effort: " allowed nil must-match)))))) + ;; Test whether the value can be interpreted as a duration before + ;; inserting it in the buffer: + (org-duration-to-minutes value) + ;; Maybe update the effort value: (unless (equal current value) (org-entry-put nil org-effort-property value)) (org-refresh-property '((effort . identity) @@ -13029,7 +12663,7 @@ Return value is an alist. Keys are properties, as upcased strings." (org-with-point-at pom (when (and (derived-mode-p 'org-mode) - (ignore-errors (org-back-to-heading t))) + (org-back-to-heading-or-point-min t)) (catch 'exit (let* ((beg (point)) (specific (and (stringp which) (upcase which))) @@ -13072,7 +12706,7 @@ strings." (push (cons "PRIORITY" (if (looking-at org-priority-regexp) (match-string-no-properties 2) - (char-to-string org-default-priority))) + (char-to-string org-priority-default))) props) (when specific (throw 'exit props))) (when (or (not specific) (string= specific "FILE")) @@ -13238,13 +12872,13 @@ unless LITERAL-NIL is non-nil." ;; Return final values. (and (not (equal value '(nil))) (nreverse value)))))) -(defun org--property-global-value (property literal-nil) - "Return value for PROPERTY in current buffer. +(defun org--property-global-or-keyword-value (property literal-nil) + "Return value for PROPERTY as defined by global properties or by keyword. Return value is a string. Return nil if property is not set -globally. Also return nil when PROPERTY is set to \"nil\", -unless LITERAL-NIL is non-nil." +globally or by keyword. Also return nil when PROPERTY is set to +\"nil\", unless LITERAL-NIL is non-nil." (let ((global - (cdr (or (assoc-string property org-file-properties t) + (cdr (or (assoc-string property org-keyword-properties t) (assoc-string property org-global-properties t) (assoc-string property org-global-properties-fixed t))))) (if literal-nil global (org-not-nil global)))) @@ -13393,12 +13027,12 @@ However, if LITERAL-NIL is set, return the string value \"nil\" instead." value))) (cond ((car v) - (org-back-to-heading t) + (org-back-to-heading-or-point-min t) (move-marker org-entry-property-inherited-from (point)) (throw 'exit nil)) - ((org-up-heading-safe)) + ((org-up-heading-or-point-min)) (t - (let ((global (org--property-global-value property literal-nil))) + (let ((global (org--property-global-or-keyword-value property literal-nil))) (cond ((not global)) (value (setq value (concat global " " value))) (t (setq value global)))) @@ -13430,8 +13064,8 @@ decreases scheduled or deadline date by one day." (user-error "Invalid property name: \"%s\"" property))) (org-with-point-at pom (if (or (not (featurep 'org-inlinetask)) (org-inlinetask-in-task-p)) - (org-back-to-heading t) - (org-with-limited-levels (org-back-to-heading t))) + (org-back-to-heading-or-point-min t) + (org-with-limited-levels (org-back-to-heading-or-point-min t))) (let ((beg (point))) (cond ((equal property "TODO") @@ -13501,7 +13135,10 @@ COLUMN formats in the current buffer." (props (append (and specials org-special-properties) (and defaults (cons org-effort-property org-default-properties)) - nil))) + ;; Get property names from #+PROPERTY keywords as well + (mapcar (lambda (s) + (nth 0 (split-string s))) + (cdar (org-collect-keywords '("PROPERTY"))))))) (org-with-wide-buffer (goto-char (point-min)) (while (re-search-forward org-property-start-re nil t) @@ -13549,7 +13186,15 @@ COLUMN formats in the current buffer." (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)))))) + (sort (delete-dups + (append props + ;; for each xxx_ALL property, make sure the bare + ;; xxx property is also included + (delq nil (mapcar (lambda (p) + (and (string-match-p "._ALL\\'" p) + (substring p 0 -4))) + props)))) + (lambda (a b) (string< (upcase a) (upcase b)))))) (defun org-property-values (key) "List all non-nil values of property KEY in current buffer." @@ -13567,21 +13212,26 @@ COLUMN formats in the current buffer." Do nothing if the drawer already exists. The newly created drawer is immediately hidden." (org-with-wide-buffer + ;; Set point to the position where the drawer should be inserted. (if (or (not (featurep 'org-inlinetask)) (org-inlinetask-in-task-p)) - (org-back-to-heading t) - (org-with-limited-levels (org-back-to-heading t))) - (forward-line) - (when (looking-at-p org-planning-line-re) (forward-line)) + (org-back-to-heading-or-point-min t) + (org-with-limited-levels (org-back-to-heading-or-point-min t))) + (if (org-before-first-heading-p) + (while (and (org-at-comment-p) (bolp)) (forward-line)) + (progn + (forward-line) + (when (looking-at-p org-planning-line-re) (forward-line)))) (unless (looking-at-p org-property-drawer-re) ;; Make sure we start editing a line from current entry, not from ;; next one. It prevents extending text properties or overlays ;; belonging to the latter. - (when (bolp) (backward-char)) - (let ((begin (1+ (point))) + (when (and (bolp) (> (point) (point-min))) (backward-char)) + (let ((begin (if (bobp) (point) (1+ (point)))) (inhibit-read-only t)) - (insert "\n:PROPERTIES:\n:END:") - (org-flag-drawer t nil (line-end-position 0) (point)) - (when (eobp) (insert "\n")) + (unless (bobp) (insert "\n")) + (insert ":PROPERTIES:\n:END:") + (org-flag-region (line-end-position 0) (point) t 'outline) + (when (or (eobp) (= begin (point-min))) (insert "\n")) (org-indent-region begin (point)))))) (defun org-insert-drawer (&optional arg drawer) @@ -13761,7 +13411,8 @@ part of the buffer." (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))))))))) + (throw 'exit (progn (org-back-to-heading-or-point-min t) + (point))))))))) (defun org-delete-property (property) "In the current entry, delete PROPERTY." @@ -13831,8 +13482,8 @@ completion." (setq vals (org-with-point-at pom (append org-todo-keywords-1 '(""))))) ((equal property "PRIORITY") - (let ((n org-lowest-priority)) - (while (>= n org-highest-priority) + (let ((n org-priority-lowest)) + (while (>= n org-priority-highest) (push (char-to-string n) vals) (setq n (1- n))))) ((equal property "CATEGORY")) @@ -13897,15 +13548,9 @@ completion." (defun org-find-olp (path &optional this-buffer) "Return a marker pointing to the entry at outline path OLP. -If anything goes wrong, throw an error. -You can wrap this call to catch the error like this: - - (condition-case msg - (org-mobile-locate-entry (match-string 4)) - (error (nth 1 msg))) - -The return value will then be either a string with the error message, -or a marker if everything is OK. +If anything goes wrong, throw an error, and if you need to do +something based on this error, you can catch it with +`condition-case'. If THIS-BUFFER is set, the outline path does not contain a file, only headings." @@ -14081,16 +13726,16 @@ non-nil." (defun org-time-stamp-inactive (&optional arg) "Insert an inactive time stamp. -An inactive time stamp is enclosed in square brackets instead of angle -brackets. It is inactive in the sense that it does not trigger agenda entries, -does not link to the calendar and cannot be changed with the S-cursor keys. -So these are more for recording a certain time/date. +An inactive time stamp is enclosed in square brackets instead of +angle brackets. It is inactive in the sense that it does not +trigger agenda entries. So these are more for recording a +certain time/date. 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 included. -When called with two universal prefix arguments, insert an active time stamp +When called with two universal prefix arguments, insert an inactive time stamp with the current time without prompting the user." (interactive "P") (org-time-stamp arg 'inactive)) @@ -14106,7 +13751,6 @@ with the current time without prompting the user." (defvar org-overriding-default-time nil) ; dynamically scoped (defvar org-read-date-overlay nil) -(defvar org-dcst nil) ; dynamically scoped (defvar org-read-date-history nil) (defvar org-read-date-final-answer nil) (defvar org-read-date-analyze-futurep nil) @@ -14176,7 +13820,6 @@ user." (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)) @@ -14295,7 +13938,7 @@ user." " " (or org-ans1 org-ans2))) (org-end-time-was-given nil) (f (org-read-date-analyze ans org-def org-defdecode)) - (fmts (if org-dcst + (fmts (if org-display-custom-times org-time-stamp-custom-formats org-time-stamp-formats)) (fmt (if (or org-with-time @@ -14961,7 +14604,7 @@ signaled." (cdr errdata)))))))) (defun org-days-to-iso-week (days) - "Return the iso week number." + "Return the ISO week number." (require 'cal-iso) (car (calendar-iso-from-absolute days))) @@ -16041,7 +15684,7 @@ environment remains unintended." ;; Get indentation of next line unless at column 0. (let ((ind (if (bolp) 0 (save-excursion - (org-return-indent) + (org-return t) (prog1 (current-indentation) (when (progn (skip-chars-forward " \t") (eolp)) (delete-region beg (point))))))) @@ -16086,7 +15729,10 @@ looks only before point, not after." (catch 'exit (let ((pos (point)) (dodollar (member "$" (plist-get org-format-latex-options :matchers))) - (lim (save-excursion (org-backward-paragraph) (point))) + (lim (progn + (re-search-backward (concat "^\\(" paragraph-start "\\)") nil + 'move) + (point))) dd-on str (start 0) m re) (goto-char pos) (when dodollar @@ -16154,7 +15800,7 @@ BEG and END are buffer positions." ;; 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 + temporary-file-directory default-directory) 'overlays nil 'forbuffer org-preview-latex-default-process)))) @@ -16265,6 +15911,10 @@ Some of the options can be changed using the variable (if (string= (match-string 0 value) "$$") (insert "\\[" (substring value 2 -2) "\\]") (insert "\\(" (substring value 1 -1) "\\)")))) + ((eq processing-type 'html) + (goto-char beg) + (delete-region beg end) + (insert (org-format-latex-as-html value))) ((assq processing-type org-preview-latex-process-alist) ;; Process to an image. (cl-incf cnt) @@ -16393,7 +16043,7 @@ inspection." (write-region mathml nil mathml-file)) (when (called-interactively-p 'any) (message mathml))) - ((message "LaTeX to MathML conversion failed") + ((warn "LaTeX to MathML conversion failed") (message shell-command-output))) (delete-file tmp-in-file) (when (file-exists-p tmp-out-file) @@ -16430,6 +16080,14 @@ inspection." ;; Failed conversion. Return the LaTeX fragment verbatim latex-frag))) +(defun org-format-latex-as-html (latex-fragment) + "Convert LATEX-FRAGMENT to HTML. +This uses `org-latex-to-html-convert-command', which see." + (let ((cmd (format-spec org-latex-to-html-convert-command + `((?i . ,latex-fragment))))) + (message "Running %s" cmd) + (shell-command-to-string cmd))) + (defun org--get-display-dpi () "Get the DPI of the display. The function assumes that the display has the same pixel width in @@ -16499,12 +16157,16 @@ a HTML file." (setq bg (org-latex-color :background)) (setq bg (org-latex-color-format (if (string= bg "Transparent") "white" bg)))) + ;; Remove TeX \par at end of snippet to avoid trailing space. + (if (string-suffix-p string "\n") + (aset string (1- (length string)) ?%) + (setq string (concat string "%"))) (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" + "\\definecolor{fg}{rgb}{" fg "}%\n" + "\\definecolor{bg}{rgb}{" bg "}%\n" + "\n\\pagecolor{bg}%\n" "\n{\\color{fg}\n" string "\n}\n" @@ -16631,16 +16293,60 @@ INCLUDE-LINKED is passed to `org-display-inline-images'." "No images to display inline"))))) (defun org-redisplay-inline-images () - "Refresh the display of inline images." + "Assure display of inline images and refresh them." (interactive) - (if (not org-inline-image-overlays) - (org-toggle-inline-images) - (org-toggle-inline-images) + (org-toggle-inline-images) + (unless org-inline-image-overlays (org-toggle-inline-images))) ;; For without-x builds. (declare-function image-refresh "image" (spec &optional frame)) +(defcustom org-display-remote-inline-images 'skip + "How to display remote inline images. +Possible values of this option are: + +skip Don't display remote images. +download Always download and display remote images. +cache Display remote images, and open them in separate buffers + for caching. Silently update the image buffer when a file + change is detected." + :group 'org-appearance + :package-version '(Org . "9.4") + :type '(choice + (const :tag "Ignore remote images" skip) + (const :tag "Always display remote images" download) + (const :tag "Display and silently update remote images" cache)) + :safe #'symbolp) + +(defun org--create-inline-image (file width) + "Create image located at FILE, or return nil. +WIDTH is the width of the image. The image may not be created +according to the value of `org-display-remote-inline-images'." + (let* ((remote? (file-remote-p file)) + (file-or-data + (pcase org-display-remote-inline-images + ((guard (not remote?)) file) + (`download (with-temp-buffer + (set-buffer-multibyte nil) + (insert-file-contents-literally file) + (buffer-string))) + (`cache (let ((revert-without-query '("."))) + (with-current-buffer (find-file-noselect file) + (buffer-string)))) + (`skip nil) + (other + (message "Invalid value of `org-display-remote-inline-images': %S" + other) + nil)))) + (when file-or-data + (create-image file-or-data + (and (image-type-available-p 'imagemagick) + width + 'imagemagick) + remote? + :width width)))) + (defun org-display-inline-images (&optional include-linked refresh beg end) "Display inline images. @@ -16759,11 +16465,7 @@ buffer boundaries with possible narrowing." 'org-image-overlay))) (if (and (car-safe old) refresh) (image-refresh (overlay-get (cdr old) 'display)) - (let ((image (create-image file - (and (image-type-available-p 'imagemagick) - width 'imagemagick) - nil - :width width))) + (let ((image (org--create-inline-image file width))) (when image (let ((ov (make-overlay (org-element-property :begin link) @@ -16778,7 +16480,9 @@ buffer boundaries with possible narrowing." (overlay-put ov 'modification-hooks (list 'org-display-inline-remove-overlay)) - (overlay-put ov 'keymap image-map) + (when (<= 26 emacs-major-version) + (cl-assert (boundp 'image-map)) + (overlay-put ov 'keymap image-map)) (push ov org-inline-image-overlays)))))))))))))))) (defun org-display-inline-remove-overlay (ov after _beg _end &optional _len) @@ -16865,7 +16569,7 @@ overwritten, and the table is not marked as requiring realignment." (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. + "Check if editing 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 (when (and org-catch-invisible-edits @@ -17025,7 +16729,7 @@ word constituents." (defvar org-ctrl-c-ctrl-c-hook nil "Hook for functions attaching themselves to `C-c C-c'. -This can be used to add additional functionality to the C-c C-c +This can be used to add additional functionality to the `C-c C-c' key which executes context-dependent commands. This hook is run before any other test, while `org-ctrl-c-ctrl-c-final-hook' is run after the last test. @@ -17038,7 +16742,7 @@ context is wrong, just do nothing and return nil.") (defvar org-ctrl-c-ctrl-c-final-hook nil "Hook for functions attaching themselves to `C-c C-c'. -This can be used to add additional functionality to the C-c C-c +This can be used to add additional functionality to the `C-c C-c' key which executes context-dependent commands. This hook is run after any other test, while `org-ctrl-c-ctrl-c-hook' is run before the first test. @@ -17403,13 +17107,15 @@ individual commands for more information." (call-interactively (if org-edit-timestamp-down-means-later 'org-timestamp-down 'org-timestamp-up))) ((and (not (eq org-support-shift-select 'always)) - org-enable-priority-commands + org-priority-enable-commands (org-at-heading-p)) (call-interactively 'org-priority-up)) ((and (not org-support-shift-select) (org-at-item-p)) (call-interactively 'org-previous-item)) ((org-clocktable-try-shift 'up arg)) - ((org-at-table-p) (org-table-move-cell-up)) + ((and (not (eq org-support-shift-select 'always)) + (org-at-table-p)) + (org-table-move-cell-up)) ((run-hook-with-args-until-success 'org-shiftup-final-hook)) (org-support-shift-select (org-call-for-shift-select 'previous-line)) @@ -17429,13 +17135,15 @@ individual commands for more information." (call-interactively (if org-edit-timestamp-down-means-later 'org-timestamp-up 'org-timestamp-down))) ((and (not (eq org-support-shift-select 'always)) - org-enable-priority-commands + org-priority-enable-commands (org-at-heading-p)) (call-interactively 'org-priority-down)) ((and (not org-support-shift-select) (org-at-item-p)) (call-interactively 'org-next-item)) ((org-clocktable-try-shift 'down arg)) - ((org-at-table-p) (org-table-move-cell-down)) + ((and (not (eq org-support-shift-select 'always)) + (org-at-table-p)) + (org-table-move-cell-down)) ((run-hook-with-args-until-success 'org-shiftdown-final-hook)) (org-support-shift-select (org-call-for-shift-select 'next-line)) @@ -17473,7 +17181,9 @@ This does one of the following: (org-at-property-p)) (call-interactively 'org-property-next-allowed-value)) ((org-clocktable-try-shift 'right arg)) - ((org-at-table-p) (org-table-move-cell-right)) + ((and (not (eq org-support-shift-select 'always)) + (org-at-table-p)) + (org-table-move-cell-right)) ((run-hook-with-args-until-success 'org-shiftright-final-hook)) (org-support-shift-select (org-call-for-shift-select 'forward-char)) @@ -17511,7 +17221,9 @@ This does one of the following: (org-at-property-p)) (call-interactively 'org-property-previous-allowed-value)) ((org-clocktable-try-shift 'left arg)) - ((org-at-table-p) (org-table-move-cell-left)) + ((and (not (eq org-support-shift-select 'always)) + (org-at-table-p)) + (org-table-move-cell-left)) ((run-hook-with-args-until-success 'org-shiftleft-final-hook)) (org-support-shift-select (org-call-for-shift-select 'backward-char)) @@ -17588,23 +17300,12 @@ this numeric value." (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." + "Call `org-table-hline-and-move' or `org-insert-heading'." (interactive) (cond ((org-at-table-p) (call-interactively 'org-table-hline-and-move)) (t (call-interactively 'org-insert-heading)))) -(defun org-find-visible () - (let ((s (point))) - (while (and (not (= (point-max) (setq s (next-overlay-change s)))) - (get-char-property s 'invisible))) - s)) -(defun org-find-invisible () - (let ((s (point))) - (while (and (not (= (point-max) (setq s (next-overlay-change s)))) - (not (get-char-property s 'invisible)))) - s)) - (defun org-copy-visible (beg end) "Copy the visible parts of the region." (interactive "r") @@ -17712,6 +17413,7 @@ Otherwise, return a user error." (pcase (org-element-type context) (`footnote-reference (org-edit-footnote-reference)) (`inline-src-block (org-edit-inline-src-code)) + (`latex-fragment (org-edit-latex-fragment)) (`timestamp (if (eq 'inactive (org-element-property :type context)) (call-interactively #'org-time-stamp-inactive) (call-interactively #'org-time-stamp))) @@ -17723,14 +17425,19 @@ Otherwise, return a user error." This command does many different things, depending on context: +- If column view is active, in agenda or org buffers, quit it. + +- If there are highlights, remove them. + - If a function in `org-ctrl-c-ctrl-c-hook' recognizes this location, this is what we do. - If the cursor is on a statistics cookie, update it. -- If the cursor is in a headline, prompt for tags and insert them - into the current line, aligned to `org-tags-column'. When called - with prefix arg, realign all tags in the current buffer. +- If the cursor is in a headline, in an agenda or an org buffer, + prompt for tags and insert them into the current line, aligned + to `org-tags-column'. When called with prefix arg, realign all + tags in the current buffer. - If the cursor is in one of the special #+KEYWORD lines, this triggers scanning the buffer for these lines and updating the @@ -17764,6 +17471,7 @@ This command does many different things, depending on context: inhibited by setting `org-babel-no-eval-on-ctrl-c-ctrl-c'." (interactive "P") (cond + ((bound-and-true-p org-columns-overlays) (org-columns-quit)) ((or (bound-and-true-p org-clock-overlays) org-occur-highlights) (when (boundp 'org-clock-overlays) (org-clock-remove-overlays)) (org-remove-occur-highlights) @@ -17785,6 +17493,7 @@ This command does many different things, depending on context: src-block statistics-cookie table table-cell table-row timestamp) t)) + (radio-list-p (org-at-radio-list-p)) (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. @@ -17831,39 +17540,81 @@ This command does many different things, depending on context: ;; 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)))))) + (if (or radio-list-p + (and (boundp org-list-checkbox-radio-mode) + org-list-checkbox-radio-mode)) + (org-toggle-radio-button arg) + (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))))))) + (`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. + (if (or radio-list-p + (and (boundp org-list-checkbox-radio-mode) + org-list-checkbox-radio-mode)) + (org-toggle-radio-button arg) + (let* ((begin (org-element-property :contents-begin context)) + (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))) + (when (equal + (org-list-write-struct + struct (org-list-parents-alist struct) old-struct) + old-struct) + (message "Cannot update this checkbox")) + (org-update-checkbox-count-maybe)))) (`keyword (let ((org-inhibit-startup-visibility-stuff t) (org-startup-align-all-tables nil)) @@ -17872,40 +17623,6 @@ This command does many different things, depending on context: (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)) - (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))) - (when (equal - (org-list-write-struct - struct (org-list-parents-alist struct) old-struct) - old-struct) - (message "Cannot update this checkbox")) - (org-update-checkbox-count-maybe))) ((or `property-drawer `node-property) (call-interactively #'org-property-action)) (`radio-target @@ -17949,6 +17666,7 @@ Use `\\[org-edit-special]' to edit table.el tables")) "`\\[org-ctrl-c-ctrl-c]' can do nothing useful here")))))))) (defun org-mode-restart () +"Restart `org-mode'." (interactive) (let ((indent-status (bound-and-true-p org-indent-mode))) (funcall major-mode) @@ -17980,13 +17698,17 @@ Move point to the beginning of first heading or end of buffer." (defun org-kill-note-or-show-branches () "Abort storing current note, or show just branches." (interactive) - (if org-finish-function - (let ((org-note-abort t)) - (funcall org-finish-function)) - (if (org-before-first-heading-p) - (org-show-branches-buffer) - (outline-hide-subtree) - (outline-show-branches)))) + (cond (org-finish-function + (let ((org-note-abort t)) (funcall org-finish-function))) + ((org-before-first-heading-p) + (org-show-branches-buffer) + (org-hide-archived-subtrees (point-min) (point-max))) + (t + (let ((beg (progn (org-back-to-heading) (point))) + (end (save-excursion (org-end-of-subtree t t) (point)))) + (outline-hide-subtree) + (outline-show-branches) + (org-hide-archived-subtrees beg end))))) (defun org-delete-indentation (&optional arg) "Join current line to previous and fix whitespace at join. @@ -17994,7 +17716,9 @@ Move point to the beginning of first heading or end of buffer." 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." +I.e. with a non-nil optional argument, join the line with the +following one. If there is a region then join the lines in that +region." (interactive "*P") (if (save-excursion (beginning-of-line (if arg 1 0)) @@ -18019,7 +17743,8 @@ With a non-nil optional argument, join it to the following one." ((not tags-column)) ;no tags (org-auto-align-tags (org-align-tags)) (t (org--align-tags-here tags-column)))) ;preserve tags column - (delete-indentation arg))) + (let ((current-prefix-arg arg)) + (call-interactively #'delete-indentation)))) (defun org-open-line (n) "Insert a new row in tables, call `open-line' elsewhere. @@ -18031,20 +17756,31 @@ call `open-line' on the very first character." (org-table-insert-row) (open-line n))) -(defun org-return (&optional indent) +(defun org--newline (indent arg interactive) + "Call `newline-and-indent' or just `newline'. +If INDENT is non-nil, call `newline-and-indent' with ARG to +indent unconditionally; otherwise, call `newline' with ARG and +INTERACTIVE, which can trigger indentation if +`electric-indent-mode' is enabled." + (if indent + (org-newline-and-indent arg) + (newline arg interactive))) + +(defun org-return (&optional indent arg interactive) "Goto next table row or insert a newline. Calls `org-table-next-row' or `newline', depending on context. When optional INDENT argument is non-nil, call -`newline-and-indent' instead of `newline'. +`newline-and-indent' with ARG, otherwise call `newline' with ARG +and INTERACTIVE. 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) + (interactive "i\nP\np") (let ((context (if org-return-follows-link (org-element-context) (org-element-at-point)))) (cond @@ -18095,45 +17831,47 @@ object (e.g., within a comment). In these case, you need to use (t (org--align-tags-here tags-column))) ;preserve tags column (end-of-line) (org-show-entry) - (if indent (newline-and-indent) (newline)) + (org--newline indent arg interactive) (when string (save-excursion (insert (org-trim string)))))) ;; In a list, make sure indenting keeps trailing text within. - ((and indent - (not (eolp)) + ((and (not (eolp)) (org-element-lineage context '(item))) (let ((trailing-data (delete-and-extract-region (point) (line-end-position)))) - (newline-and-indent) + (org--newline indent arg interactive) (save-excursion (insert trailing-data)))) (t ;; Do not auto-fill when point is in an Org property drawer. (let ((auto-fill-function (and (not (org-at-property-p)) auto-fill-function))) - (if indent - (newline-and-indent) - (newline))))))) + (org--newline indent arg interactive)))))) -(defun org-return-indent () - "Goto next table row or insert a newline and indent. -Calls `org-table-next-row' or `newline-and-indent', depending on -context. See the individual commands for more information." +(defun org-return-and-maybe-indent () + "Goto next table row, or insert a newline. +Call `org-table-next-row' or `org-return', depending on context. +See the individual commands for more information. + +When inserting a newline, indent the new line if +`electric-indent-mode' is disabled." (interactive) - (org-return t)) + (org-return (not electric-indent-mode))) (defun org-ctrl-c-tab (&optional arg) "Toggle columns width in a table, or show children. Call `org-table-toggle-column-width' if point is in a table. -Otherwise, call `org-show-children'. ARG is the level to hide." +Otherwise provide a compact view of the children. ARG is the +level to hide." (interactive "p") - (if (org-at-table-p) - (call-interactively #'org-table-toggle-column-width) - (if (org-before-first-heading-p) - (progn - (org-flag-above-first-heading) - (outline-hide-sublevels (or arg 1)) - (goto-char (point-min))) - (outline-hide-subtree) - (org-show-children arg)))) + (cond + ((org-at-table-p) + (call-interactively #'org-table-toggle-column-width)) + ((org-before-first-heading-p) + (save-excursion + (org-flag-above-first-heading) + (outline-hide-sublevels (or arg 1)))) + (t + (outline-hide-subtree) + (org-show-children arg)))) (defun org-ctrl-c-star () "Compute table, or change heading status of lines. @@ -18277,79 +18015,14 @@ an argument, unconditionally call `org-insert-heading'." (t #'org-insert-heading))))) ;;; Menu entries - (defsubst org-in-subtree-not-table-p () "Are we in a subtree and not in a table?" (and (not (org-before-first-heading-p)) (not (org-at-table-p)))) ;; 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)] - ["Next Field" org-cycle (org-at-table-p)] - ["Previous Field" org-shifttab (org-at-table-p)] - ["Next Row" org-return (org-at-table-p)] - "--" - ["Blank Field" org-table-blank-field (org-at-table-p)] - ["Edit Field" org-table-edit-field (org-at-table-p)] - ["Copy Field from Above" org-table-copy-down (org-at-table-p)] - "--" - ("Column" - ["Move Column Left" org-metaleft (org-at-table-p)] - ["Move Column Right" org-metaright (org-at-table-p)] - ["Delete Column" org-shiftmetaleft (org-at-table-p)] - ["Insert Column" org-shiftmetaright (org-at-table-p)] - ["Shrink Column" org-table-toggle-column-width (org-at-table-p)]) - ("Row" - ["Move Row Up" org-metaup (org-at-table-p)] - ["Move Row Down" org-metadown (org-at-table-p)] - ["Delete Row" org-shiftmetaup (org-at-table-p)] - ["Insert Row" org-shiftmetadown (org-at-table-p)] - ["Sort lines in region" org-table-sort-lines (org-at-table-p)] - "--" - ["Insert Hline" org-ctrl-c-minus (org-at-table-p)]) - ("Rectangle" - ["Copy Rectangle" org-copy-special (org-at-table-p)] - ["Cut Rectangle" org-cut-special (org-at-table-p)] - ["Paste Rectangle" org-paste-special (org-at-table-p)] - ["Fill Rectangle" org-table-wrap-region (org-at-table-p)]) - "--" - ("Calculate" - ["Set Column Formula" org-table-eval-formula (org-at-table-p)] - ["Set Field Formula" (org-table-eval-formula '(4)) :active (org-at-table-p) :keys "C-u C-c ="] - ["Edit Formulas" org-edit-special (org-at-table-p)] - "--" - ["Recalculate line" org-table-recalculate (org-at-table-p)] - ["Recalculate all" (lambda () (interactive) (org-table-recalculate '(4))) :active (org-at-table-p) :keys "C-u C-c *"] - ["Iterate all" (lambda () (interactive) (org-table-recalculate '(16))) :active (org-at-table-p) :keys "C-u C-u C-c *"] - "--" - ["Toggle Recalculate Mark" org-table-rotate-recalc-marks (org-at-table-p)] - "--" - ["Sum Column/Rectangle" org-table-sum - (or (org-at-table-p) (org-region-active-p))] - ["Which Column?" org-table-current-column (org-at-table-p)]) - ["Debug Formulas" - org-table-toggle-formula-debugger - :style toggle :selected (bound-and-true-p org-table-formula-debug)] - ["Show Col/Row Numbers" - org-table-toggle-coordinate-overlays - :style toggle - :selected (bound-and-true-p org-table-overlay-coordinates)] - "--" - ["Create" org-table-create (not (org-at-table-p))] - ["Convert Region" org-table-convert-region (not (org-at-table-p 'any))] - ["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] - "--" - ("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" + `("Org" ("Show/Hide" ["Cycle Visibility" org-cycle :active (or (bobp) (outline-on-heading-p))] ["Cycle Global Visibility" org-shifttab :active (not (org-at-table-p))] @@ -18369,8 +18042,6 @@ an argument, unconditionally call `org-insert-heading'." "--" ["Jump" org-goto t]) ("Edit Structure" - ["Refile Subtree" org-refile (org-in-subtree-not-table-p)] - "--" ["Move Subtree Up" org-metaup (org-at-heading-p)] ["Move Subtree Down" org-metadown (org-at-heading-p)] "--" @@ -18393,6 +18064,7 @@ an argument, unconditionally call `org-insert-heading'." ["Convert to odd/even levels" org-convert-to-oddeven-levels t]) ("Editing" ["Emphasis..." org-emphasize t] + ["Add block structure" org-insert-structure-template t] ["Edit Source Example" org-edit-special t] "--" ["Footnote new/jump" org-footnote-action t] @@ -18402,8 +18074,7 @@ an argument, unconditionally call `org-insert-heading'." "--" ["Move Subtree to Archive file" org-archive-subtree (org-in-subtree-not-table-p)] ["Toggle ARCHIVE tag" org-toggle-archive-tag (org-in-subtree-not-table-p)] - ["Move subtree to Archive sibling" org-archive-to-archive-sibling (org-in-subtree-not-table-p)] - ) + ["Move subtree to Archive sibling" org-archive-to-archive-sibling (org-in-subtree-not-table-p)]) "--" ("Hyperlinks" ["Store Link (Global)" org-store-link t] @@ -18520,13 +18191,6 @@ an argument, unconditionally call `org-insert-heading'." (org-inside-LaTeX-fragment-p)] ["Insert citation" org-reftex-citation t]) "--" - ("MobileOrg" - ["Push Files and Views" org-mobile-push t] - ["Get Captured and Flagged" org-mobile-pull t] - ["Find FLAGGED Tasks" (org-agenda nil "?") :active t :keys "\\[org-agenda] ?"] - "--" - ["Setup" (progn (require 'org-mobile) (customize-group 'org-mobile)) t]) - "--" ("Documentation" ["Show Version" org-version t] ["Info Documentation" org-info t] @@ -18534,8 +18198,7 @@ an argument, unconditionally call `org-insert-heading'." ("Customize" ["Browse Org Group" org-customize t] "--" - ["Expand This Menu" org-create-customize-menu - (fboundp 'customize-menu-create)]) + ["Expand This Menu" org-create-customize-menu t]) ["Send bug report" org-submit-bug-report t] "--" ("Refresh/Reload" @@ -18543,6 +18206,70 @@ an argument, unconditionally call `org-insert-heading'." ["Reload Org (after update)" org-reload t] ["Reload Org uncompiled" (org-reload t) :active t :keys "C-u C-c C-x !"]))) +(easy-menu-define org-tbl-menu org-mode-map "Org Table menu" + '("Table" + ["Align" org-ctrl-c-ctrl-c :active (org-at-table-p)] + ["Next Field" org-cycle (org-at-table-p)] + ["Previous Field" org-shifttab (org-at-table-p)] + ["Next Row" org-return (org-at-table-p)] + "--" + ["Blank Field" org-table-blank-field (org-at-table-p)] + ["Edit Field" org-table-edit-field (org-at-table-p)] + ["Copy Field from Above" org-table-copy-down (org-at-table-p)] + "--" + ("Column" + ["Move Column Left" org-metaleft (org-at-table-p)] + ["Move Column Right" org-metaright (org-at-table-p)] + ["Delete Column" org-shiftmetaleft (org-at-table-p)] + ["Insert Column" org-shiftmetaright (org-at-table-p)] + ["Shrink Column" org-table-toggle-column-width (org-at-table-p)]) + ("Row" + ["Move Row Up" org-metaup (org-at-table-p)] + ["Move Row Down" org-metadown (org-at-table-p)] + ["Delete Row" org-shiftmetaup (org-at-table-p)] + ["Insert Row" org-shiftmetadown (org-at-table-p)] + ["Sort lines in region" org-table-sort-lines (org-at-table-p)] + "--" + ["Insert Hline" org-ctrl-c-minus (org-at-table-p)]) + ("Rectangle" + ["Copy Rectangle" org-copy-special (org-at-table-p)] + ["Cut Rectangle" org-cut-special (org-at-table-p)] + ["Paste Rectangle" org-paste-special (org-at-table-p)] + ["Fill Rectangle" org-table-wrap-region (org-at-table-p)]) + "--" + ("Calculate" + ["Set Column Formula" org-table-eval-formula (org-at-table-p)] + ["Set Field Formula" (org-table-eval-formula '(4)) :active (org-at-table-p) :keys "C-u C-c ="] + ["Edit Formulas" org-edit-special (org-at-table-p)] + "--" + ["Recalculate line" org-table-recalculate (org-at-table-p)] + ["Recalculate all" (lambda () (interactive) (org-table-recalculate '(4))) :active (org-at-table-p) :keys "C-u C-c *"] + ["Iterate all" (lambda () (interactive) (org-table-recalculate '(16))) :active (org-at-table-p) :keys "C-u C-u C-c *"] + "--" + ["Toggle Recalculate Mark" org-table-rotate-recalc-marks (org-at-table-p)] + "--" + ["Sum Column/Rectangle" org-table-sum + (or (org-at-table-p) (org-region-active-p))] + ["Which Column?" org-table-current-column (org-at-table-p)]) + ["Debug Formulas" + org-table-toggle-formula-debugger + :style toggle :selected (bound-and-true-p org-table-formula-debug)] + ["Show Col/Row Numbers" + org-table-toggle-coordinate-overlays + :style toggle + :selected (bound-and-true-p org-table-overlay-coordinates)] + "--" + ["Create" org-table-create (not (org-at-table-p))] + ["Convert Region" org-table-convert-region (not (org-at-table-p 'any))] + ["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] + "--" + ("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"]))) + (defun org-info (&optional node) "Read documentation for Org in the info system. With optional NODE, go directly to that node." @@ -18579,14 +18306,22 @@ information about your Org version and configuration." (erase-buffer) (insert "You are about to submit a bug report to the Org mailing list. -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. +If your report is about Org installation, please read this section: +https://orgmode.org/org.html#Installation + +Please read https://orgmode.org/org.html#Feedback on how to make +a good report, it will help Org contributors fixing your problem. + +Search https://lists.gnu.org/archive/html/emacs-orgmode/ to see +if the issue you are about to raise has already been dealt with. -HOWEVER, some variables you have customized may contain private +We also would like to add your full Org and Outline configuration +to the bug report. It will help us debugging the issue. + +*HOWEVER*, some variables you have customized may contain private information. The names of customers, colleagues, or friends, might -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 +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 configuration ") @@ -18616,6 +18351,7 @@ Your bug report will be posted to the Org mailing list. (defun org-install-agenda-files-menu () + "Install agenda file menu." (let ((bl (buffer-list))) (save-excursion (while bl @@ -18708,20 +18444,17 @@ With prefix arg UNCOMPILED, load the uncompiled versions." (interactive) (org-load-modules-maybe) (org-require-autoloaded-modules) - (if (fboundp 'customize-menu-create) - (progn - (easy-menu-change - '("Org") "Customize" - `(["Browse Org group" org-customize t] - "--" - ,(customize-menu-create 'org) - ["Set" Custom-set t] - ["Save" Custom-save t] - ["Reset to Current" Custom-reset-current t] - ["Reset to Saved" Custom-reset-saved t] - ["Reset to Standard Settings" Custom-reset-standard t])) - (message "\"Org\"-menu now contains full customization menu")) - (error "Cannot expand menu (outdated version of cus-edit.el)"))) + (easy-menu-change + '("Org") "Customize" + `(["Browse Org group" org-customize t] + "--" + ,(customize-menu-create 'org) + ["Set" Custom-set t] + ["Save" Custom-save t] + ["Reset to Current" Custom-reset-current t] + ["Reset to Saved" Custom-reset-saved t] + ["Reset to Standard Settings" Custom-reset-standard t])) + (message "\"Org\"-menu now contains full customization menu")) ;;;; Miscellaneous stuff @@ -18851,7 +18584,8 @@ and :keyword." (when (memq 'org-formula faces) (push (list :table-special (previous-single-property-change p 'face) - (next-single-property-change p 'face)) clist))) + (next-single-property-change p 'face)) + clist))) ((org-at-table-p 'any) (push (list :table-table) clist))) (goto-char p) @@ -18864,14 +18598,16 @@ and :keyword." (re-search-backward "[ \t]*\\(#+BEGIN: clocktable\\)" nil t)) (match-beginning 1)) (and (re-search-forward "[ \t]*#\\+END:?" nil t) - (match-end 0))) clist)) + (match-end 0))) + clist)) ((org-in-src-block-p) (push (list :src-block (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)))) + (match-beginning 0))) + clist)))) (goto-char p) ;; Now the small context @@ -18881,20 +18617,24 @@ and :keyword." ((memq 'org-link faces) (push (list :link (previous-single-property-change p 'face) - (next-single-property-change p 'face)) clist)) + (next-single-property-change p 'face)) + clist)) ((memq 'org-special-keyword faces) (push (list :keyword (previous-single-property-change p 'face) - (next-single-property-change p 'face)) clist)) + (next-single-property-change p 'face)) + clist)) ((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) + (overlay-start o) (overlay-end o)) + clist) (push (list :latex-preview - (overlay-start o) (overlay-end o)) clist)) + (overlay-start o) (overlay-end o)) + clist)) ((org-inside-LaTeX-fragment-p) ;; FIXME: positions wrong. (push (list :latex-fragment (point) (point)) clist))) @@ -19023,7 +18763,7 @@ earliest time on the cursor date that Org treats as that date (let (date day defd tp hod mod) (when with-time (setq tp (get-text-property (point) 'time)) - (when (and tp (string-match "\\([0-9][0-9]\\):\\([0-9][0-9]\\)" tp)) + (when (and tp (string-match "\\([0-2]?[0-9]\\):\\([0-5][0-9]\\)" tp)) (setq hod (string-to-number (match-string 1 tp)) mod (string-to-number (match-string 2 tp)))) (or tp (let ((now (decode-time))) @@ -19081,6 +18821,11 @@ ELEMENT." (t (goto-char start) (current-indentation)))) + ((and + (eq org-adapt-indentation 'headline-data) + (memq type '(planning clock node-property property-drawer drawer))) + (org--get-expected-indentation + (org-element-property :parent element) t)) ((memq type '(headline inlinetask nil)) (if (org-match-line "[ \t]*$") (org--get-expected-indentation element t) @@ -19094,7 +18839,8 @@ ELEMENT." ;; At first line: indent according to previous sibling, if any, ;; ignoring footnote definitions and inline tasks, or parent's ;; contents. - ((= (line-beginning-position) start) + ((and ( = (line-beginning-position) start) + (eq org-adapt-indentation t)) (catch 'exit (while t (if (= (point-min) start) (throw 'exit 0) @@ -19119,7 +18865,7 @@ ELEMENT." (org--get-expected-indentation (org-element-property :parent previous) t)))))))))) ;; Otherwise, move to the first non-blank line above. - (t + ((not (eq org-adapt-indentation 'headline-data)) (beginning-of-line) (let ((pos (point))) (skip-chars-backward " \r\t\n") @@ -19161,7 +18907,9 @@ ELEMENT." (goto-char start) (current-indentation))) ;; In any other case, indent like the current line. - (t (current-indentation))))))))) + (t (current-indentation))))) + ;; Finally, no indentation is needed, fall back to 0. + (t (current-indentation)))))) (defun org--align-node-property () "Align node property at point. @@ -19219,31 +18967,28 @@ list structure. Instead, use \\`\\[org-shiftmetaleft]' or \ Also align node properties according to `org-property-format'." (interactive) - (cond - ((org-at-heading-p) 'noindent) - (t + (unless (org-at-heading-p) (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) + nil) ((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) + (< (point) + (org-with-point-at (org-element-property :end element) + (skip-chars-backward " \t\n") + (line-beginning-position 2)))) + nil) ((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-with-point-at (org-element-property :end element) + (skip-chars-backward " \t\n") + (line-beginning-position)))) (org-babel-do-key-sequence-in-edit-buffer (kbd "TAB"))) (t (let ((column (org--get-expected-indentation element nil))) @@ -19255,7 +19000,7 @@ Also align node properties according to `org-property-format'." (when (eq type 'node-property) (let ((column (current-column))) (org--align-node-property) - (org-move-to-column column))))))))) + (org-move-to-column column)))))))) (defun org-indent-region (start end) "Indent each non-blank line in the region. @@ -19670,12 +19415,17 @@ filling the current element." (unwind-protect (progn (goto-char (region-end)) + (skip-chars-backward " \t\n") (while (> (point) start) - (org-backward-paragraph) - (org-fill-element justify))) + (org-fill-element justify) + (org-backward-paragraph))) (goto-char origin) (set-marker origin nil)))) - (t (org-fill-element justify))) + (t + (save-excursion + (when (org-match-line "[ \t]*$") + (skip-chars-forward " \t\n")) + (org-fill-element justify)))) ;; If we didn't change anything in the buffer (and the buffer was ;; previously unmodified), then flip the modification status back ;; to "unchanged". @@ -20377,7 +20127,8 @@ depending on context." (if (<= end (point)) ;on tags part (kill-region (point) (line-end-position)) (kill-region (point) end))) - (org-align-tags)) + ;; Only align tags when we are still on a heading: + (if (org-at-heading-p) (org-align-tags))) (t (kill-region (point) (line-end-position))))) (defun org-yank (&optional arg) @@ -20486,8 +20237,18 @@ interactive command with similar behavior." "Call `outline-back-to-heading', but provide a better error message." (condition-case nil (outline-back-to-heading invisible-ok) - (error (error "Before first headline at position %d in buffer %s" - (point) (current-buffer))))) + (error + (user-error "Before first headline at position %d in buffer %s" + (point) (current-buffer))))) + +(defun org-back-to-heading-or-point-min (&optional invisible-ok) + "Go back to heading or first point in buffer. +If point is before first heading go to first point in buffer +instead of back to heading." + (condition-case nil + (outline-back-to-heading invisible-ok) + (error + (goto-char (point-min))))) (defun org-before-first-heading-p () "Before first heading?" @@ -20515,12 +20276,31 @@ unless optional argument NO-INHERITANCE is non-nil." (t (save-excursion (and (org-up-heading-safe) (org-in-commented-heading-p)))))) +(defun org-in-archived-heading-p (&optional no-inheritance) + "Non-nil if point is under an archived 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 ((tags (org-get-tags nil 'local))) + (and tags + (cl-some (apply-partially #'string= org-archive-tag) tags)))) + (no-inheritance nil) + (t + (save-excursion (and (org-up-heading-safe) (org-in-archived-heading-p)))))) + (defun org-at-comment-p nil "Return t if cursor is in a commented line." (save-excursion (save-match-data (beginning-of-line) - (looking-at "^[ \t]*# ")))) + (looking-at org-comment-regexp)))) + +(defun org-at-keyword-p nil + "Return t if cursor is at a keyword-line." + (save-excursion + (move-beginning-of-line 1) + (looking-at org-keyword-regexp))) (defun org-at-drawer-p nil "Return t if cursor is at a drawer keyword." @@ -20569,6 +20349,17 @@ make a significant difference in outlines with very many siblings." (re-search-backward (format "^\\*\\{1,%d\\} " level-up) nil t) (funcall outline-level))))) +(defun org-up-heading-or-point-min () + "Move to the heading line of which the present is a subheading, or point-min. +This version is needed to make point-min behave like a virtual +heading of level 0 for property-inheritance. It will return the +level of the headline found (down to 0) or nil if already at a +point before the first headline or at point-min." + (when (ignore-errors (org-back-to-heading t)) + (if (< 1 (funcall outline-level)) + (org-up-heading-safe) + (unless (= (point) (point-min)) (goto-char (point-min)))))) + (defun org-first-sibling-p () "Is this heading the first child of its parents?" (interactive) @@ -20669,28 +20460,31 @@ If there is no such heading, return nil." (defun org-end-of-subtree (&optional invisible-ok to-heading) "Goto to the end of a subtree." ;; This contains an exact copy of the original function, but it uses - ;; `org-back-to-heading', to make it work also in invisible - ;; trees. And is uses an invisible-ok argument. + ;; `org-back-to-heading-or-point-min', to make it work also in invisible + ;; trees and before first headline. And is uses an invisible-ok argument. ;; Under Emacs this is not needed, but the old outline.el needs this fix. ;; Furthermore, when used inside Org, finding the end of a large subtree ;; with many children and grandchildren etc, this can be much faster ;; than the outline version. - (org-back-to-heading invisible-ok) + (org-back-to-heading-or-point-min invisible-ok) (let ((first t) (level (funcall outline-level))) - (if (and (derived-mode-p 'org-mode) (< level 1000)) - ;; A true heading (not a plain list item), in Org - ;; This means we can easily find the end by looking - ;; only for the right number of stars. Using a regexp to do - ;; this is so much faster than using a Lisp loop. - (let ((re (concat "^\\*\\{1," (int-to-string level) "\\} "))) - (forward-char 1) - (and (re-search-forward re nil 'move) (beginning-of-line 1))) - ;; something else, do it the slow way - (while (and (not (eobp)) - (or first (> (funcall outline-level) level))) - (setq first nil) - (outline-next-heading))) + (cond ((= level 0) + (goto-char (point-max))) + ((and (derived-mode-p 'org-mode) (< level 1000)) + ;; A true heading (not a plain list item), in Org + ;; This means we can easily find the end by looking + ;; only for the right number of stars. Using a regexp to do + ;; this is so much faster than using a Lisp loop. + (let ((re (concat "^\\*\\{1," (number-to-string level) "\\} "))) + (forward-char 1) + (and (re-search-forward re nil 'move) (beginning-of-line 1)))) + (t + ;; something else, do it the slow way + (while (and (not (eobp)) + (or first (> (funcall outline-level) level))) + (setq first nil) + (outline-next-heading)))) (unless to-heading (when (memq (preceding-char) '(?\n ?\^M)) ;; Go to end of line before heading @@ -20702,26 +20496,50 @@ If there is no such heading, return nil." (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." + +When optional argument FULL is t, also skip planning information, +clocking lines and any kind of drawer. + +When FULL is non-nil but not t, skip planning information, +clocking lines and only non-regular drawers, i.e. properties and +logbook drawers." (org-back-to-heading t) (forward-line) + ;; Skip planning information. (when (looking-at-p org-planning-line-re) (forward-line)) + ;; Skip property drawer. (when (looking-at org-property-drawer-re) (goto-char (match-end 0)) (forward-line)) + ;; When FULL is not nil, skip more. (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)))))))) + (cond ;; Skip clock lines. + ((looking-at-p re) (forward-line)) + ;; Skip logbook drawer. + ((looking-at-p org-logbook-drawer-re) + (if (re-search-forward "^[ \t]*:END:[ \t]*$" end t) + (forward-line) + (throw 'exit t))) + ;; When FULL is t, skip regular drawer too. + ((and (eq full t) (looking-at-p org-drawer-regexp)) + (if (re-search-forward "^[ \t]*:END:[ \t]*$" end t) + (forward-line) + (throw 'exit t))) + (t (throw 'exit t)))))))) + +(defun org--line-fully-invisible-p () + "Return non-nil if the current line is fully invisible." + (let ((line-beg (line-beginning-position)) + (line-pos (1- (line-end-position))) + (is-invisible t)) + (while (and (< line-beg line-pos) is-invisible) + (setq is-invisible (org-invisible-p line-pos)) + (setq line-pos (1- line-pos))) + is-invisible)) (defun org-forward-heading-same-level (arg &optional invisible-ok) "Move forward to the ARG'th subheading at same level as this one. @@ -20744,8 +20562,14 @@ non-nil it will also look at invisible ones." (cond ((< l level) (setq count 0)) ((and (= l level) (or invisible-ok - (not (org-invisible-p - (line-beginning-position))))) + ;; FIXME: See commit a700fadd72 and the + ;; related discussion on why using + ;; `org--line-fully-invisible-p' is needed + ;; here, which is to serve the needs of an + ;; external package. If the change is + ;; wrong regarding Org itself, it should + ;; be removed. + (not (org--line-fully-invisible-p)))) (cl-decf count) (when (= l level) (setq result (point))))))) (goto-char result)) @@ -20758,175 +20582,332 @@ Stop at the first and last subheadings of a superior heading." (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'." + "Move to the next visible heading line. +With ARG, repeats or can move backward if negative." (interactive "p") - (org-with-limited-levels - (outline-next-visible-heading arg))) + (let ((regexp (concat "^" (org-get-limited-outline-regexp)))) + (if (< arg 0) + (beginning-of-line) + (end-of-line)) + (while (and (< arg 0) (re-search-backward regexp nil :move)) + (unless (bobp) + (while (pcase (get-char-property-and-overlay (point) 'invisible) + (`(outline . ,o) + (goto-char (overlay-start o)) + (re-search-backward regexp nil :move)) + (_ nil)))) + (cl-incf arg)) + (while (and (> arg 0) (re-search-forward regexp nil t)) + (while (pcase (get-char-property-and-overlay (point) 'invisible) + (`(outline . ,o) + (goto-char (overlay-end o)) + (re-search-forward regexp nil :move)) + (_ + (end-of-line) + nil))) ;leave the loop + (cl-decf arg)) + (if (> arg 0) (goto-char (point-max)) (beginning-of-line)))) (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'." +With ARG, repeats or can move forward if negative." (interactive "p") - (org-with-limited-levels - (outline-previous-visible-heading arg))) + (org-next-visible-heading (- arg))) + +(defun org-forward-paragraph (&optional arg) + "Move forward by a paragraph, or equivalent, unit. -(defun org-forward-paragraph () - "Move forward to beginning of next paragraph or equivalent. +With argument ARG, do it ARG times; +a negative argument ARG = -N means move backward N paragraphs. -The function moves point to the beginning of the next visible -structural element, which can be a paragraph, a table, a list -item, etc. It also provides some special moves for convenience: +The function moves point between two structural +elements (paragraphs, tables, lists, etc.). - - On an affiliated keyword, jump to the beginning of the - relative element. - - On an item or a footnote definition, move to the second - element inside, if any. - - On a table or a property drawer, jump after it. - - On a verse or source block, stop after blank lines." +It also provides the following special moves for convenience: + + - on a table or a property drawer, move to its beginning; + - on comment, example, export, source and verse blocks, stop + at blank lines; + - skip consecutive clocks, diary S-exps, and keywords." + (interactive "^p") + (unless arg (setq arg 1)) + (if (< arg 0) (org-backward-paragraph (- arg)) + (while (and (> arg 0) (not (eobp))) + (org--forward-paragraph-once) + (cl-decf arg)) + ;; Return moves left. + arg)) + +(defun org-backward-paragraph (&optional arg) + "Move backward by a paragraph, or equivalent, unit. + +With argument ARG, do it ARG times; +a negative argument ARG = -N means move forward N paragraphs. + +The function moves point between two structural +elements (paragraphs, tables, lists, etc.). + +It also provides the following special moves for convenience: + + - on a table or a property drawer, move to its beginning; + - on comment, example, export, source and verse blocks, stop + at blank lines; + - skip consecutive clocks, diary S-exps, and keywords." + (interactive "^p") + (unless arg (setq arg 1)) + (if (< arg 0) (org-forward-paragraph (- arg)) + (while (and (> arg 0) (not (bobp))) + (org--backward-paragraph-once) + (cl-decf arg)) + ;; Return moves left. + arg)) + +(defun org--paragraph-at-point () + "Return paragraph, or equivalent, element at point. + +Paragraph element at point is the element at point, with the +following special cases: + +- treat table rows (resp. node properties) as the table + \(resp. property drawer) containing them. + +- treat plain lists with an item every line as a whole. + +- treat consecutive keywords, clocks, and diary-sexps as a single + block. + +Function may return a real element, or a pseudo-element with type +`pseudo-paragraph'." + (let* ((e (org-element-at-point)) + (type (org-element-type e)) + ;; If we need to fake a new pseudo-element, triplet is + ;; + ;; (BEG END PARENT) + ;; + ;; where BEG and END are element boundaries, and PARENT the + ;; element containing it, or nil. + (triplet + (cond + ((memq type '(table property-drawer)) + (list (org-element-property :begin e) + (org-element-property :end e) + (org-element-property :parent e))) + ((memq type '(node-property table-row)) + (let ((e (org-element-property :parent e))) + (list (org-element-property :begin e) + (org-element-property :end e) + (org-element-property :parent e)))) + ((memq type '(clock diary-sexp keyword)) + (let* ((regexp (pcase type + (`clock org-clock-line-re) + (`diary-sexp "%%(") + (_ org-keyword-regexp))) + (end (if (< 0 (org-element-property :post-blank e)) + (org-element-property :end e) + (org-with-wide-buffer + (forward-line) + (while (looking-at regexp) (forward-line)) + (skip-chars-forward " \t\n") + (line-beginning-position)))) + (begin (org-with-point-at (org-element-property :begin e) + (while (and (not (bobp)) (looking-at regexp)) + (forward-line -1)) + ;; We may have gotten one line too far. + (if (looking-at regexp) + (point) + (line-beginning-position 2))))) + (list begin end (org-element-property :parent e)))) + ;; Find the full plain list containing point, the check it + ;; contains exactly one line per item. + ((let ((l (org-element-lineage e '(plain-list) t))) + (while (memq (org-element-type (org-element-property :parent l)) + '(item plain-list)) + (setq l (org-element-property :parent l))) + (and l + (org-with-point-at (org-element-property :post-affiliated l) + (forward-line (length (org-element-property :structure l))) + (= (point) (org-element-property :contents-end l))) + ;; Return value. + (list (org-element-property :begin l) + (org-element-property :end l) + (org-element-property :parent l))))) + (t nil)))) ;no triplet: return element + (pcase triplet + (`(,b ,e ,p) + (org-element-create + 'pseudo-paragraph + (list :begin b :end e :parent p :post-blank 0 :post-affiliated b))) + (_ e)))) + +(defun org--forward-paragraph-once () + "Move forward to end of paragraph or equivalent, once. +See `org-forward-paragraph'." (interactive) - (unless (eobp) - (let* ((deactivate-mark nil) - (element (org-element-at-point)) - (type (org-element-type element)) - (post-affiliated (org-element-property :post-affiliated element)) - (contents-begin (org-element-property :contents-begin element)) - (contents-end (org-element-property :contents-end element)) - (end (let ((end (org-element-property :end element)) (parent element)) - (while (and (setq parent (org-element-property :parent parent)) - (= (org-element-property :contents-end parent) end)) - (setq end (org-element-property :end parent))) - end))) - (cond ((not element) - (skip-chars-forward " \r\t\n") - (or (eobp) (beginning-of-line))) - ;; On affiliated keywords, move to element's beginning. - ((< (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 - ;; drawer. - ((memq type '(node-property table-row)) - (goto-char (org-element-property - :end (org-element-property :parent element)))) - ((memq type '(property-drawer table)) (goto-char end)) - ;; Consider blank lines as separators in verse and source - ;; blocks to ease editing. - ((memq type '(src-block verse-block)) - (when (eq type 'src-block) - (setq contents-end - (save-excursion (goto-char end) - (skip-chars-backward " \r\t\n") - (line-beginning-position)))) - (beginning-of-line) - (when (looking-at "[ \t]*$") (skip-chars-forward " \r\t\n")) - (if (not (re-search-forward "^[ \t]*$" contents-end t)) - (goto-char end) - (skip-chars-forward " \r\t\n") - (if (= (point) contents-end) (goto-char end) - (beginning-of-line)))) - ;; With no contents, just skip element. - ((not contents-begin) (goto-char end)) - ;; If contents are invisible, skip the element altogether. - ((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 - ;; instead of skipping the whole list. - (plain-list (forward-char) - (org-forward-paragraph)) - (otherwise (goto-char end)))) - ((>= (point) contents-end) (goto-char end)) - ((>= (point) contents-begin) - ;; This can only happen on paragraphs and plain lists. - (cl-case type - (paragraph (goto-char end)) - ;; At a plain list, try to move to second element in - ;; first item, if possible. - (plain-list (end-of-line) - (org-forward-paragraph)))) - ;; When contents start on the middle of a line (e.g. in - ;; items and footnote definitions), try to reach first - ;; element starting after current line. - ((> (line-end-position) contents-begin) - (end-of-line) - (org-forward-paragraph)) - (t (goto-char contents-begin)))))) - -(defun org-backward-paragraph () - "Move backward to start of previous paragraph or equivalent. - -The function moves point to the beginning of the current -structural element, which can be a paragraph, a table, a list -item, etc., or to the beginning of the previous visible one if -point is already there. It also provides some special moves for -convenience: - - - On an affiliated keyword, jump to the first one. - - On a table or a property drawer, move to its beginning. - - On comment, example, export, src and verse blocks, stop - before blank lines." + (save-restriction + (widen) + (skip-chars-forward " \t\n") + (cond + ((eobp) nil) + ;; When inside a folded part, move out of it. + ((pcase (get-char-property-and-overlay (point) 'invisible) + (`(,(or `outline `org-hide-block) . ,o) + (goto-char (overlay-end o)) + (forward-line) + t) + (_ nil))) + (t + (let* ((element (org--paragraph-at-point)) + (type (org-element-type element)) + (contents-begin (org-element-property :contents-begin element)) + (end (org-element-property :end element)) + (post-affiliated (org-element-property :post-affiliated element))) + (cond + ((eq type 'plain-list) + (forward-char) + (org--forward-paragraph-once)) + ;; If the element is folded, skip it altogether. + ((pcase (org-with-point-at post-affiliated + (get-char-property-and-overlay (line-end-position) + 'invisible)) + (`(,(or `outline `org-hide-block) . ,o) + (goto-char (overlay-end o)) + (forward-line) + t) + (_ nil))) + ;; At a greater element, move inside. + ((and contents-begin + (> contents-begin (point)) + (not (eq type 'paragraph))) + (goto-char contents-begin) + ;; Items and footnote definitions contents may not start at + ;; the beginning of the line. In this case, skip until the + ;; next paragraph. + (cond + ((not (bolp)) (org--forward-paragraph-once)) + ((org-previous-line-empty-p) (forward-line -1)) + (t nil))) + ;; Move between empty lines in some blocks. + ((memq type '(comment-block example-block export-block src-block + verse-block)) + (let ((contents-start + (org-with-point-at post-affiliated + (line-beginning-position 2)))) + (if (< (point) contents-start) + (goto-char contents-start) + (let ((contents-end + (org-with-point-at end + (skip-chars-backward " \t\n") + (line-beginning-position)))) + (cond + ((>= (point) contents-end) + (goto-char end) + (skip-chars-backward " \t\n") + (forward-line)) + ((re-search-forward "^[ \t]*\n" contents-end :move) + (forward-line -1)) + (t nil)))))) + (t + ;; Move to element's end. + (goto-char end) + (skip-chars-backward " \t\n") + (forward-line)))))))) + +(defun org--backward-paragraph-once () + "Move backward to start of paragraph or equivalent, once. +See `org-backward-paragraph'." (interactive) - (unless (bobp) - (let* ((deactivate-mark nil) - (element (org-element-at-point)) - (type (org-element-type element)) - (contents-end (org-element-property :contents-end element)) - (post-affiliated (org-element-property :post-affiliated element)) - (begin (org-element-property :begin element)) - (special? ;blocks handled specially - (memq type '(comment-block example-block export-block src-block - verse-block))) - (contents-begin - (if special? - ;; These types have no proper contents. Fake line - ;; below the block opening line as contents beginning. - (save-excursion (goto-char begin) (line-beginning-position 2)) - (org-element-property :contents-begin element)))) - (cond - ((not element) (goto-char (point-min))) - ((= (point) begin) - (backward-char) - (org-backward-paragraph)) - ((<= (point) post-affiliated) (goto-char begin)) - ;; Special behavior: on a table or a property drawer, move to - ;; its beginning. - ((memq type '(node-property table-row)) - (goto-char (org-element-property - :post-affiliated (org-element-property :parent element)))) - (special? - (if (<= (point) contents-begin) (goto-char post-affiliated) - ;; Inside a verse block, see blank lines as paragraph - ;; separators. - (let ((origin (point))) - (skip-chars-backward " \r\t\n" contents-begin) - (when (re-search-backward "^[ \t]*$" contents-begin 'move) - (skip-chars-forward " \r\t\n" origin) - (if (= (point) origin) (goto-char contents-begin) - (beginning-of-line)))))) - ((eq type 'paragraph) (goto-char contents-begin) - ;; When at first paragraph in an item or a footnote definition, - ;; move directly to beginning of line. - (let ((parent-contents - (org-element-property - :contents-begin (org-element-property :parent element)))) - (when (and parent-contents (= parent-contents contents-begin)) - (beginning-of-line)))) - ;; At the end of a greater element, move to the beginning of - ;; the last element within. - ((and contents-end (>= (point) contents-end)) - (goto-char (1- contents-end)) - (org-backward-paragraph)) - (t (goto-char (or post-affiliated begin)))) - ;; Ensure we never leave point invisible. - (when (org-invisible-p (point)) (beginning-of-visual-line))))) + (save-restriction + (widen) + (cond + ((bobp) nil) + ;; Blank lines at the beginning of the buffer. + ((and (org-match-line "^[ \t]*$") + (save-excursion (skip-chars-backward " \t\n") (bobp))) + (goto-char (point-min))) + ;; When inside a folded part, move out of it. + ((pcase (get-char-property-and-overlay (1- (point)) 'invisible) + (`(,(or `outline `org-hide-block) . ,o) + (goto-char (1- (overlay-start o))) + (org--backward-paragraph-once) + t) + (_ nil))) + (t + (let* ((element (org--paragraph-at-point)) + (type (org-element-type element)) + (begin (org-element-property :begin element)) + (post-affiliated (org-element-property :post-affiliated element)) + (contents-end (org-element-property :contents-end element)) + (end (org-element-property :end element)) + (parent (org-element-property :parent element)) + (reach + ;; Move to the visible empty line above position P, or + ;; to position P. Return t. + (lambda (p) + (goto-char p) + (when (and (org-previous-line-empty-p) + (let ((end (line-end-position 0))) + (or (= end (point-min)) + (not (org-invisible-p (1- end)))))) + (forward-line -1)) + t))) + (cond + ;; Already at the beginning of an element. + ((= begin (point)) + (cond + ;; There is a blank line above. Move there. + ((and (org-previous-line-empty-p) + (not (org-invisible-p (1- (line-end-position 0))))) + (forward-line -1)) + ;; At the beginning of the first element within a greater + ;; element. Move to the beginning of the greater element. + ((and parent (= begin (org-element-property :contents-begin parent))) + (funcall reach (org-element-property :begin parent))) + ;; Since we have to move anyway, find the beginning + ;; position of the element above. + (t + (forward-char -1) + (org--backward-paragraph-once)))) + ;; Skip paragraphs at the very beginning of footnote + ;; definitions or items. + ((and (eq type 'paragraph) + (org-with-point-at begin (not (bolp)))) + (funcall reach (progn (goto-char begin) (line-beginning-position)))) + ;; If the element is folded, skip it altogether. + ((org-with-point-at post-affiliated + (org-invisible-p (line-end-position) t)) + (funcall reach begin)) + ;; At the end of a greater element, move inside. + ((and contents-end + (<= contents-end (point)) + (not (eq type 'paragraph))) + (cond + ((memq type '(footnote-definition plain-list)) + (skip-chars-backward " \t\n") + (org--backward-paragraph-once)) + ((= contents-end (point)) + (forward-char -1) + (org--backward-paragraph-once)) + (t + (goto-char contents-end)))) + ;; Move between empty lines in some blocks. + ((and (memq type '(comment-block example-block export-block src-block + verse-block)) + (let ((contents-start + (org-with-point-at post-affiliated + (line-beginning-position 2)))) + (when (> (point) contents-start) + (let ((contents-end + (org-with-point-at end + (skip-chars-backward " \t\n") + (line-beginning-position)))) + (if (> (point) contents-end) + (progn (goto-char contents-end) t) + (skip-chars-backward " \t\n" begin) + (re-search-backward "^[ \t]*\n" contents-start :move) + t)))))) + ;; Move to element's start. + (t + (funcall reach begin)))))))) (defun org-forward-element () "Move forward by one element. @@ -21108,10 +21089,11 @@ ones already marked." (set-mark (save-excursion (goto-char (mark)) - (goto-char (org-element-property :end (org-element-at-point))))) + (goto-char (org-element-property :end (org-element-at-point))) + (point))) (let ((element (org-element-at-point))) (end-of-line) - (push-mark (org-element-property :end element) t t) + (push-mark (min (point-max) (org-element-property :end element)) t t) (goto-char (org-element-property :begin element)))))) (defun org-narrow-to-element () @@ -21231,4 +21213,8 @@ Started from `gnus-info-find-node'." (run-hooks 'org-load-hook) +;; Local variables: +;; generated-autoload-file: "org-loaddefs.el" +;; End: + ;;; org.el ends here diff --git a/lisp/org/ox-ascii.el b/lisp/org/ox-ascii.el index 972b58a9912..e5240f5c895 100644 --- a/lisp/org/ox-ascii.el +++ b/lisp/org/ox-ascii.el @@ -31,6 +31,8 @@ (require 'ox-publish) (require 'cl-lib) +;;; Function Declarations + (declare-function aa2u "ext:ascii-art-to-unicode" ()) ;;; Define Back-End @@ -954,7 +956,7 @@ channel." ((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) + ((org-export-custom-protocol-maybe link anchor 'ascii info) nil) (t (concat (org-ascii--fill-string @@ -1270,7 +1272,8 @@ CONTENTS is nil. INFO is a plist holding contextual information." (org-ascii--justify-element (org-ascii--box-string (org-remove-indentation - (org-element-property :value fixed-width)) info) + (org-element-property :value fixed-width)) + info) fixed-width info)) @@ -1569,7 +1572,7 @@ DESC is the description part of the link, or the empty string. INFO is a plist holding contextual information." (let ((type (org-element-property :type link))) (cond - ((org-export-custom-protocol-maybe link desc 'ascii)) + ((org-export-custom-protocol-maybe link desc 'ascii info)) ((string= type "coderef") (let ((ref (org-element-property :path link))) (format (org-export-get-coderef-format ref desc) @@ -1605,13 +1608,11 @@ INFO is a plist holding contextual information." ;; Don't know what to do. Signal it. (_ "???")))) (t - (let ((raw-link (concat (org-element-property :type link) - ":" - (org-element-property :path link)))) - (if (not (org-string-nw-p desc)) (format "<%s>" raw-link) + (let ((path (org-element-property :raw-link link))) + (if (not (org-string-nw-p desc)) (format "<%s>" path) (concat (format "[%s]" desc) (and (not (plist-get info :ascii-links-to-notes)) - (format " (<%s>)" raw-link))))))))) + (format " (<%s>)" path))))))))) ;;;; Node Properties diff --git a/lisp/org/ox-beamer.el b/lisp/org/ox-beamer.el index 23656db444c..66589fac5d9 100644 --- a/lisp/org/ox-beamer.el +++ b/lisp/org/ox-beamer.el @@ -731,7 +731,7 @@ 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." - (or (org-export-custom-protocol-maybe link contents 'beamer) + (or (org-export-custom-protocol-maybe link contents 'beamer info) ;; 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))) diff --git a/lisp/org/ox-html.el b/lisp/org/ox-html.el index 678506a6756..d2f24f5c6e4 100644 --- a/lisp/org/ox-html.el +++ b/lisp/org/ox-html.el @@ -62,7 +62,6 @@ (export-block . org-html-export-block) (export-snippet . org-html-export-snippet) (fixed-width . org-html-fixed-width) - (footnote-definition . org-html-footnote-definition) (footnote-reference . org-html-footnote-reference) (headline . org-html-headline) (horizontal-rule . org-html-horizontal-rule) @@ -121,6 +120,7 @@ (:html-link-home "HTML_LINK_HOME" nil org-html-link-home) (:html-link-up "HTML_LINK_UP" nil org-html-link-up) (:html-mathjax "HTML_MATHJAX" nil "" space) + (:html-equation-reference-format "HTML_EQUATION_REFERENCE_FORMAT" nil org-html-equation-reference-format t) (:html-postamble nil "html-postamble" org-html-postamble) (:html-preamble nil "html-preamble" org-html-preamble) (:html-head "HTML_HEAD" nil org-html-head newline) @@ -152,6 +152,7 @@ (: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-prefer-user-labels nil nil org-html-prefer-user-labels) (:html-self-link-headlines nil nil org-html-self-link-headlines) (:html-table-align-individual-fields nil nil org-html-table-align-individual-fields) @@ -232,50 +233,26 @@ property on the headline itself.") (defconst org-html-scripts "" "Basic JavaScript that is needed by HTML files produced by Org mode.") @@ -311,7 +288,7 @@ for the JavaScript code in this tag. } pre.src { position: relative; - overflow: visible; + overflow: auto; padding-top: 1.2em; } pre.src:before { @@ -532,73 +509,22 @@ means to use the maximum value consistent with other options." (defcustom org-html-infojs-template " " "The template for the export style additions when org-info.js is used. Option settings will replace the %MANAGER-OPTIONS cookie." :group 'org-export-html - :version "24.4" - :package-version '(Org . "8.0") + :package-version '(Org . "9.4") :type 'string) (defun org-html-infojs-install-script (exp-plist _backend) @@ -811,6 +737,24 @@ but without \"name\" attribute." :type 'boolean :safe #'booleanp) +(defcustom org-html-prefer-user-labels nil + "When non-nil use user-defined names and ID over internal ones. + +By default, Org generates its own internal ID values during HTML +export. This process ensures that these values are unique and +valid, but the keys are not available in advance of the export +process, and not so readable. + +When this variable is non-nil, Org will use NAME keyword, or the +real name of the target to create the ID attribute. + +Independently of this variable, however, CUSTOM_ID are always +used as a reference." + :group 'org-export-html + :package-version '(Org . "9.4") + :type 'boolean + :safe #'booleanp) + ;;;; Inlinetasks (defcustom org-html-format-inlinetask-function @@ -834,6 +778,24 @@ The function should return the string to be exported." ;;;; LaTeX +(defcustom org-html-equation-reference-format "\\eqref{%s}" + "The MathJax command to use when referencing equations. + +This is a format control string that expects a single string argument +specifying the label that is being referenced. The argument is +generated automatically on export. + +The default is to wrap equations in parentheses (using \"\\eqref{%s}\)\". + +Most common values are: + + \\eqref{%s} Wrap the equation in parentheses + \\ref{%s} Do not wrap the equation in parentheses" + :group 'org-export-html + :package-version '(Org . "9.4") + :type 'string + :safe t) + (defcustom org-html-with-latex org-export-with-latex "Non-nil means process LaTeX math snippets. @@ -847,6 +809,8 @@ e.g. \"tex:mathjax\". Allowed values are: `verbatim' Keep everything in verbatim `mathjax', t Do MathJax preprocessing and arrange for MathJax.js to be loaded. + `html' Use `org-latex-to-html-convert-command' to convert + LaTeX fragments to HTML. SYMBOL Any symbol defined in `org-preview-latex-process-alist', e.g., `dvipng'." :group 'org-export-html @@ -884,10 +848,9 @@ link to the image." :type 'boolean) (defcustom org-html-inline-image-rules - '(("file" . "\\.\\(jpeg\\|jpg\\|png\\|gif\\|svg\\)\\'") - ("attachment" . "\\.\\(jpeg\\|jpg\\|png\\|gif\\|svg\\)\\'") - ("http" . "\\.\\(jpeg\\|jpg\\|png\\|gif\\|svg\\)\\'") - ("https" . "\\.\\(jpeg\\|jpg\\|png\\|gif\\|svg\\)\\'")) + `(("file" . ,(regexp-opt '(".jpeg" ".jpg" ".png" ".gif" ".svg"))) + ("http" . ,(regexp-opt '(".jpeg" ".jpg" ".png" ".gif" ".svg"))) + ("https" . ,(regexp-opt '(".jpeg" ".jpg" ".png" ".gif" ".svg")))) "Rules characterizing image files that can be inlined into HTML. A rule consists in an association whose key is the type of link to consider, and value is a regexp that will be matched against @@ -1350,9 +1313,10 @@ like that: \"%%\"." (string :tag "Format string")))) (defcustom org-html-validation-link - "Validate" + "Validate" "Link to HTML validation service." :group 'org-export-html + :package-version '(Org . "9.4") :type 'string) (defcustom org-html-creator-string @@ -1662,6 +1626,36 @@ attribute with a nil value will be omitted from the result." "\"" """ (org-html-encode-plain-text item)))) (setcar output (format "%s=\"%s\"" key value)))))))) +(defun org-html--reference (datum info &optional named-only) + "Return an appropriate reference for DATUM. + +DATUM is an element or a `target' type object. INFO is the +current export state, as a plist. + +When NAMED-ONLY is non-nil and DATUM has no NAME keyword, return +nil. This doesn't apply to headlines, inline tasks, radio +targets and targets." + (let* ((type (org-element-type datum)) + (user-label + (org-element-property + (pcase type + ((or `headline `inlinetask) :CUSTOM_ID) + ((or `radio-target `target) :value) + (_ :name)) + datum))) + (cond + ((and user-label + (or (plist-get info :html-prefer-user-labels) + ;; Used CUSTOM_ID property unconditionally. + (memq type '(headline inlinetask)))) + user-label) + ((and named-only + (not (memq type '(headline inlinetask radio-target target))) + (not user-label)) + nil) + (t + (org-export-get-reference datum info))))) + (defun org-html--wrap-image (contents info &optional caption label) "Wrap CONTENTS string within an appropriate environment for images. INFO is a plist used as a communication channel. When optional @@ -1693,7 +1687,8 @@ a communication channel." (org-html--make-attribute-string (org-combine-plists (list :src source - :alt (if (string-match-p "^ltxpng/" source) + :alt (if (string-match-p + (concat "^" org-preview-latex-image-directory) source) (org-html-encode-plain-text (org-find-text-property-in-string 'org-latex-src source)) (file-name-nondirectory source))) @@ -1853,13 +1848,8 @@ INFO is a plist used as a communication channel." (title (if (org-string-nw-p title) title "‎")) (author (and (plist-get info :with-author) (let ((auth (plist-get info :author))) - (and auth - ;; Return raw Org syntax, skipping non - ;; exportable objects. - (org-element-interpret-data - (org-element-map auth - (cons 'plain-text org-element-all-objects) - 'identity info)))))) + ;; Return raw Org syntax. + (and auth (org-element-interpret-data auth))))) (description (plist-get info :description)) (keywords (plist-get info :keywords)) (charset (or (and org-html-coding-system @@ -1882,7 +1872,7 @@ INFO is a plist used as a communication channel." charset) "\n" (let ((viewport-options (cl-remove-if-not (lambda (cell) (org-string-nw-p (cadr cell))) - (plist-get info :html-viewport)))) + (plist-get info :html-viewport)))) (and viewport-options (concat (org-html-close-tag @@ -2213,7 +2203,8 @@ is the language used for CODE, as a string, or nil." ;; htmlize (setq code (let ((output-type org-html-htmlize-output-type) - (font-prefix org-html-htmlize-font-prefix)) + (font-prefix org-html-htmlize-font-prefix) + (inhibit-read-only t)) (with-temp-buffer ;; Switch to language-specific mode. (funcall lang-mode) @@ -2372,8 +2363,7 @@ INFO is a plist used as a communication channel." (org-export-get-tags headline info)))) (format "%s" ;; Label. - (or (org-element-property :CUSTOM_ID headline) - (org-export-get-reference headline info)) + (org-html--reference headline info) ;; Body. (concat (and (not (org-export-low-level-p headline info)) @@ -2401,8 +2391,7 @@ of listings as a string, or nil if it is empty." (org-html--translate "Listing %d:" info)))) (mapconcat (lambda (entry) - (let ((label (and (org-element-property :name entry) - (org-export-get-reference entry info))) + (let ((label (org-html--reference entry info t)) (title (org-trim (org-export-data (or (org-export-get-caption entry t) @@ -2440,8 +2429,7 @@ of tables as a string, or nil if it is empty." (org-html--translate "Table %d:" info)))) (mapconcat (lambda (entry) - (let ((label (and (org-element-property :name entry) - (org-export-get-reference entry info))) + (let ((label (org-html--reference entry info t)) (title (org-trim (org-export-data (or (org-export-get-caption entry t) @@ -2542,11 +2530,11 @@ information." (if (plist-get attributes :textarea) (org-html--textarea-block example-block) (format "
\n%s
" - (let* ((name (org-element-property :name example-block)) + (let* ((reference (org-html--reference example-block info)) (a (org-html--make-attribute-string - (if (or (not name) (plist-member attributes :id)) + (if (or (not reference) (plist-member attributes :id)) attributes - (plist-put attributes :id name))))) + (plist-put attributes :id reference))))) (if (org-string-nw-p a) (concat " " a) "")) (org-html-format-code example-block info))))) @@ -2622,8 +2610,7 @@ holding contextual information." (full-text (funcall (plist-get info :html-format-headline-function) todo todo-type priority text tags info)) (contents (or contents "")) - (id (or (org-element-property :CUSTOM_ID headline) - (org-export-get-reference headline info))) + (id (org-html--reference headline info)) (formatted-text (if (plist-get info :html-self-link-headlines) (format "%s" id full-text) @@ -2649,8 +2636,7 @@ holding contextual information." (first-content (car (org-element-contents headline)))) (format "<%s id=\"%s\" class=\"%s\">%s%s\n" (org-html--container headline info) - (concat "outline-container-" - (org-export-get-reference headline info)) + (format "outline-container-%s" id) (concat (format "outline-%d" level) (and extra-class " ") extra-class) @@ -2711,8 +2697,7 @@ contextual information." (org-element-property :value inline-src-block) lang)) (label - (let ((lbl (and (org-element-property :name inline-src-block) - (org-export-get-reference inline-src-block info)))) + (let ((lbl (org-html--reference inline-src-block info t))) (if (not lbl) "" (format " id=\"%s\"" lbl))))) (format "%s" lang label code))) @@ -2848,12 +2833,13 @@ CONTENTS is nil. INFO is a plist holding contextual information." (defun org-html-format-latex (latex-frag processing-type info) "Format a LaTeX fragment LATEX-FRAG into HTML. PROCESSING-TYPE designates the tool used for conversion. It can -be `mathjax', `verbatim', nil, t or symbols in +be `mathjax', `verbatim', `html', nil, t or symbols in `org-preview-latex-process-alist', e.g., `dvipng', `dvisvgm' or `imagemagick'. See `org-html-with-latex' for more information. INFO is a plist containing export properties." (let ((cache-relpath "") (cache-dir "")) - (unless (eq processing-type 'mathjax) + (unless (or (eq processing-type 'mathjax) + (eq processing-type 'html)) (let ((bfn (or (buffer-file-name) (make-temp-name (expand-file-name "latex" temporary-file-directory)))) @@ -2903,6 +2889,12 @@ used as a predicate for `org-export-get-ordinal' or a value to (string-match-p org-latex-math-environments-re (org-element-property :value element))) +(defun org-html--latex-environment-numbered-p (element) + "Non-nil when ELEMENT contains a numbered LaTeX math environment. +Starred and \"displaymath\" environments are not numbered." + (not (string-match-p "\\`[ \t]*\\\\begin{\\(.*\\*\\|displaymath\\)}" + (org-element-property :value element)))) + (defun org-html--unlabel-latex-environment (latex-frag) "Change environment in LATEX-FRAG string to an unnumbered one. For instance, change an 'equation' environment to 'equation*'." @@ -2921,12 +2913,14 @@ CONTENTS is nil. INFO is a plist holding contextual information." (latex-frag (org-remove-indentation (org-element-property :value latex-environment))) (attributes (org-export-read-attribute :attr_html latex-environment)) - (label (and (org-element-property :name latex-environment) - (org-export-get-reference latex-environment info))) - (caption (number-to-string - (org-export-get-ordinal - latex-environment info nil - #'org-html--math-environment-p)))) + (label (org-html--reference latex-environment info t)) + (caption (and (org-html--latex-environment-numbered-p latex-environment) + (number-to-string + (org-export-get-ordinal + latex-environment info nil + (lambda (l _) + (and (org-html--math-environment-p l) + (org-html--latex-environment-numbered-p l)))))))) (cond ((memq processing-type '(t mathjax)) (org-html-format-latex @@ -2942,10 +2936,10 @@ CONTENTS is nil. INFO is a plist holding contextual information." (org-html--unlabel-latex-environment latex-frag) processing-type info))) (when (and formula-link (string-match "file:\\([^]]*\\)" formula-link)) - (org-html--wrap-latex-environment - (org-html--format-image - (match-string 1 formula-link) attributes info) - info caption label)))) + (let ((source (org-export-file-uri (match-string 1 formula-link)))) + (org-html--wrap-latex-environment + (org-html--format-image source attributes info) + info caption label))))) (t (org-html--wrap-latex-environment latex-frag info caption label))))) ;;;; Latex Fragment @@ -2958,11 +2952,14 @@ CONTENTS is nil. INFO is a plist holding contextual information." (cond ((memq processing-type '(t mathjax)) (org-html-format-latex latex-frag 'mathjax info)) + ((memq processing-type '(t html)) + (org-html-format-latex latex-frag 'html info)) ((assq processing-type org-preview-latex-process-alist) (let ((formula-link (org-html-format-latex latex-frag processing-type info))) (when (and formula-link (string-match "file:\\([^]]*\\)" formula-link)) - (org-html--format-image (match-string 1 formula-link) nil info)))) + (let ((source (org-export-file-uri (match-string 1 formula-link)))) + (org-html--format-image source nil info))))) (t latex-frag)))) ;;;; Line Break @@ -3044,7 +3041,9 @@ images, set it to: DESC is the description part of the link, or the empty string. INFO is a plist holding contextual information. See `org-export-data'." - (let* ((link-org-files-as-html-maybe + (let* ((html-ext (plist-get info :html-extension)) + (dot (when (> (length html-ext) 0) ".")) + (link-org-files-as-html-maybe (lambda (raw-path info) ;; Treat links to `file.org' as links to `file.html', if ;; needed. See `org-html-link-org-files-as-html'. @@ -3052,8 +3051,7 @@ INFO is a plist holding contextual information. See ((and (plist-get info :html-link-org-files-as-html) (string= ".org" (downcase (file-name-extension raw-path ".")))) - (concat (file-name-sans-extension raw-path) "." - (plist-get info :html-extension))) + (concat (file-name-sans-extension raw-path) dot html-ext)) (t raw-path)))) (type (org-element-property :type link)) (raw-path (org-element-property :path link)) @@ -3063,7 +3061,7 @@ INFO is a plist holding contextual information. See (cond ((member type '("http" "https" "ftp" "mailto" "news")) (url-encode-url (concat type ":" raw-path))) - ((string= type "file") + ((string= "file" type) ;; During publishing, turn absolute file names belonging ;; to base directory into relative file names. Otherwise, ;; append "file" protocol to absolute file name. @@ -3114,7 +3112,7 @@ INFO is a plist holding contextual information. See (if (org-string-nw-p attr) (concat " " attr) "")))) (cond ;; Link type is handled by a special function. - ((org-export-custom-protocol-maybe link desc 'html)) + ((org-export-custom-protocol-maybe link desc 'html info)) ;; Image file. ((and (plist-get info :html-inline-images) (org-export-inline-image-p @@ -3152,8 +3150,7 @@ INFO is a plist holding contextual information. See (org-element-property :raw-link link) info)))) ;; Link points to a headline. (`headline - (let ((href (or (org-element-property :CUSTOM_ID destination) - (org-export-get-reference destination info))) + (let ((href (org-html--reference destination info)) ;; What description to use? (desc ;; Case 1: Headline is numbered and LINK has no @@ -3177,11 +3174,11 @@ INFO is a plist holding contextual information. See (eq 'latex-environment (org-element-type destination)) (eq 'math (org-latex--environment-type destination))) ;; Caption and labels are introduced within LaTeX - ;; environment. Use "eqref" macro to refer to those in - ;; the document. - (format "\\eqref{%s}" - (org-export-get-reference destination info)) - (let* ((ref (org-export-get-reference destination info)) + ;; environment. Use "ref" or "eqref" macro, depending on user + ;; preference to refer to those in the document. + (format (plist-get info :html-equation-reference-format) + (org-html--reference destination info)) + (let* ((ref (org-html--reference destination info)) (org-html-standalone-image-predicate #'org-html--has-caption-p) (counter-predicate @@ -3278,8 +3275,7 @@ the plist used as a communication channel." info nil #'org-html-standalone-image-p)) " " raw)))) - (label (and (org-element-property :name paragraph) - (org-export-get-reference paragraph info)))) + (label (org-html--reference paragraph info))) (org-html--wrap-image contents info caption label))) ;; Regular paragraph. (t (format "\n%s

" @@ -3385,17 +3381,17 @@ holding contextual information." ;;;; Quote Block -(defun org-html-quote-block (quote-block contents _info) +(defun org-html-quote-block (quote-block contents info) "Transcode a QUOTE-BLOCK element from Org to HTML. CONTENTS holds the contents of the block. INFO is a plist holding contextual information." (format "\n%s" - (let* ((name (org-element-property :name quote-block)) + (let* ((reference (org-html--reference quote-block info t)) (attributes (org-export-read-attribute :attr_html quote-block)) (a (org-html--make-attribute-string - (if (or (not name) (plist-member attributes :id)) + (if (or (not reference) (plist-member attributes :id)) attributes - (plist-put attributes :id name))))) + (plist-put attributes :id reference))))) (if (org-string-nw-p a) (concat " " a) "")) contents)) @@ -3430,7 +3426,7 @@ holding contextual information." "Transcode a RADIO-TARGET object from Org to HTML. TEXT is the text of the target. INFO is a plist holding contextual information." - (let ((ref (org-export-get-reference radio-target info))) + (let ((ref (org-html--reference radio-target info))) (org-html--anchor ref text nil info))) ;;;; Special Block @@ -3449,11 +3445,11 @@ holding contextual information." (if class (concat class " " block-type) block-type))))) (let* ((contents (or contents "")) - (name (org-element-property :name special-block)) + (reference (org-html--reference special-block info)) (a (org-html--make-attribute-string - (if (or (not name) (plist-member attributes :id)) + (if (or (not reference) (plist-member attributes :id)) attributes - (plist-put attributes :id name)))) + (plist-put attributes :id reference)))) (str (if (org-string-nw-p a) (concat " " a) ""))) (if html5-fancy (format "<%s%s>\n%s" block-type str contents block-type) @@ -3469,8 +3465,7 @@ contextual information." (org-html--textarea-block src-block) (let* ((lang (org-element-property :language src-block)) (code (org-html-format-code src-block info)) - (label (let ((lbl (and (org-element-property :name src-block) - (org-export-get-reference src-block info)))) + (label (let ((lbl (org-html--reference src-block info t))) (if lbl (format " id=\"%s\"" lbl) ""))) (klipsify (and (plist-get info :html-klipsify-src) (member lang '("javascript" "js" @@ -3665,8 +3660,7 @@ contextual information." (attributes (org-html--make-attribute-string (org-combine-plists - (and (org-element-property :name table) - (list :id (org-export-get-reference table info))) + (list :id (org-html--reference table info t)) (and (not (org-html-html5-p info)) (plist-get info :html-table-attributes)) (org-export-read-attribute :attr_html table)))) @@ -3713,7 +3707,7 @@ contextual information." "Transcode a TARGET object from Org to HTML. CONTENTS is nil. INFO is a plist holding contextual information." - (let ((ref (org-export-get-reference target info))) + (let ((ref (org-html--reference target info))) (org-html--anchor ref nil nil info))) ;;;; Timestamp @@ -3852,9 +3846,11 @@ file-local settings. Return output file's name." (interactive) - (let* ((extension (concat "." (or (plist-get ext-plist :html-extension) - org-html-extension - "html"))) + (let* ((extension (concat + (when (> (length org-html-extension) 0) ".") + (or (plist-get ext-plist :html-extension) + org-html-extension + "html"))) (file (org-export-output-file-name extension subtreep)) (org-export-coding-system org-html-coding-system)) (org-export-to-file 'html file @@ -3870,9 +3866,10 @@ publishing directory. Return output file name." (org-publish-org-to 'html filename - (concat "." (or (plist-get plist :html-extension) - org-html-extension - "html")) + (concat (when (> (length org-html-extension) 0) ".") + (or (plist-get plist :html-extension) + org-html-extension + "html")) plist pub-dir)) diff --git a/lisp/org/ox-icalendar.el b/lisp/org/ox-icalendar.el index 5968d4ee649..0f890534a8a 100644 --- a/lisp/org/ox-icalendar.el +++ b/lisp/org/ox-icalendar.el @@ -367,7 +367,8 @@ A headline is blocked when either (defun org-icalendar-use-UTC-date-time-p () "Non-nil when `org-icalendar-date-time-format' requires UTC time." (char-equal (elt org-icalendar-date-time-format - (1- (length org-icalendar-date-time-format))) ?Z)) + (1- (length org-icalendar-date-time-format))) + ?Z)) (defvar org-agenda-default-appointment-duration) ; From org-agenda.el. (defun org-icalendar-convert-timestamp (timestamp keyword &optional end tz) @@ -763,10 +764,10 @@ Return VTODO component as a string." "SEQUENCE:1\n" (format "PRIORITY:%d\n" (let ((pri (or (org-element-property :priority entry) - org-default-priority))) - (floor (- 9 (* 8. (/ (float (- org-lowest-priority pri)) - (- org-lowest-priority - org-highest-priority))))))) + org-priority-default))) + (floor (- 9 (* 8. (/ (float (- org-priority-lowest pri)) + (- org-priority-lowest + org-priority-highest))))))) (format "STATUS:%s\n" (if (eq (org-element-property :todo-type entry) 'todo) "NEEDS-ACTION" diff --git a/lisp/org/ox-latex.el b/lisp/org/ox-latex.el index edb3150796f..32d1d43a5f3 100644 --- a/lisp/org/ox-latex.el +++ b/lisp/org/ox-latex.el @@ -30,6 +30,8 @@ (require 'ox) (require 'ox-publish) +;;; Function Declarations + (defvar org-latex-default-packages-alist) (defvar org-latex-packages-alist) (defvar orgtbl-exp-regexp) @@ -736,8 +738,9 @@ environment." :safe #'stringp) (defcustom org-latex-inline-image-rules - `(("file" . ,(regexp-opt - '("pdf" "jpeg" "jpg" "png" "ps" "eps" "tikz" "pgf" "svg")))) + `(("file" . ,(rx "." + (or "pdf" "jpeg" "jpg" "png" "ps" "eps" "tikz" "pgf" "svg") + eos))) "Rules characterizing image files that can be inlined into LaTeX. A rule consists in an association whose key is the type of link @@ -750,8 +753,7 @@ pdflatex, pdf, jpg and png images are OK. When processing through dvi to Postscript, only ps and eps are allowed. The default we use here encompasses both." :group 'org-export-latex - :version "24.4" - :package-version '(Org . "8.0") + :package-version '(Org . "9.4") :type '(alist :key-type (string :tag "Type") :value-type (regexp :tag "Path"))) @@ -1239,7 +1241,7 @@ calling `org-latex-compile'." :package-version '(Org . "8.3") :type '(repeat (cons - (string :tag "Regexp") + (regexp :tag "Regexp") (string :tag "Message")))) @@ -1586,6 +1588,7 @@ INFO is a plist used as a communication channel." lang)))) `((?a . ,(org-export-data (plist-get info :author) info)) (?t . ,(org-export-data (plist-get info :title) info)) + (?s . ,(org-export-data (plist-get info :subtitle) info)) (?k . ,(org-export-data (org-latex--wrap-latex-math-block (plist-get info :keywords) info) info)) @@ -2171,25 +2174,27 @@ contextual information." "Transcode an ITEM element from Org to LaTeX. CONTENTS holds the contents of the item. INFO is a plist holding contextual information." - (let* ((counter - (let ((count (org-element-property :counter item)) - (level - ;; Determine level of current item to determine the - ;; correct LaTeX counter to use (enumi, enumii...). - (let ((parent item) (level 0)) - (while (memq (org-element-type - (setq parent (org-export-get-parent parent))) - '(plain-list item)) - (when (and (eq (org-element-type parent) 'plain-list) - (eq (org-element-property :type parent) - 'ordered)) - (cl-incf level))) - level))) - (and count - (< level 5) - (format "\\setcounter{enum%s}{%s}\n" - (nth (1- level) '("i" "ii" "iii" "iv")) - (1- count))))) + (let* ((orderedp (eq (org-element-property + :type (org-export-get-parent item)) + 'ordered)) + (level + ;; Determine level of current item to determine the + ;; correct LaTeX counter to use (enumi, enumii...). + (let ((parent item) (level 0)) + (while (memq (org-element-type + (setq parent (org-export-get-parent parent))) + '(plain-list item)) + (when (and (eq (org-element-type parent) 'plain-list) + (eq (org-element-property :type parent) + 'ordered)) + (cl-incf level))) + level)) + (count (org-element-property :counter item)) + (counter (and count + (< level 5) + (format "\\setcounter{enum%s}{%s}\n" + (nth (1- level) '("i" "ii" "iii" "iv")) + (1- count)))) (checkbox (cl-case (org-element-property :checkbox item) (on "$\\boxtimes$") (off "$\\square$") @@ -2208,9 +2213,11 @@ contextual information." "\\item" (cond ((and checkbox tag) - (format "[{%s %s}] %s" checkbox tag tag-footnotes)) + (format (if orderedp "{%s %s} %s" "[{%s %s}] %s") + checkbox tag tag-footnotes)) ((or checkbox tag) - (format "[{%s}] %s" (or checkbox tag) tag-footnotes)) + (format (if orderedp "{%s} %s" "[{%s}] %s") + (or checkbox tag) tag-footnotes)) ;; Without a tag or a check-box, if CONTENTS starts with ;; an opening square bracket, add "\relax" to "\item", ;; unless the brackets comes from an initial export @@ -2382,8 +2389,11 @@ used as a communication channel." (format "[%s]" (plist-get info :latex-default-figure-position))) (t "")))) (center - (if (plist-member attr :center) (plist-get attr :center) - (plist-get info :latex-images-centered))) + (cond + ;; If link is an image link, do not center. + ((eq 'link (org-element-type (org-export-get-parent link))) nil) + ((plist-member attr :center) (plist-get attr :center)) + (t (plist-get info :latex-images-centered)))) (comment-include (if (plist-get attr :comment-include) "%" "")) ;; It is possible to specify scale or width and height in ;; the ATTR_LATEX line, and also via default variables. @@ -2425,7 +2435,8 @@ used as a communication channel." (format "\\resizebox{%s}{%s}{%s}" (if (org-string-nw-p width) width "!") (if (org-string-nw-p height) height "!") - image-code))))) + image-code)) + (t image-code)))) ;; For other images: ;; - add scale, or width and height to options. ;; - include the image with \includegraphics. @@ -2517,15 +2528,16 @@ INFO is a plist holding contextual information. See (imagep (org-export-inline-image-p link (plist-get info :latex-inline-image-rules))) (path (org-latex--protect-text - (cond ((member type '("http" "https" "ftp" "mailto" "doi")) - (concat type ":" raw-path)) - ((string= type "file") - (org-export-file-uri raw-path)) - (t - raw-path))))) + (pcase type + ((or "http" "https" "ftp" "mailto" "doi") + (concat type ":" raw-path)) + ("file" + (org-export-file-uri raw-path)) + (_ + raw-path))))) (cond ;; Link type is handled by a special function. - ((org-export-custom-protocol-maybe link desc 'latex)) + ((org-export-custom-protocol-maybe link desc 'latex info)) ;; Image file. (imagep (org-latex--inline-image link info)) ;; Radio link: Transcode target's contents and use them as link's @@ -2576,7 +2588,9 @@ INFO is a plist holding contextual information. See ;; equivalent line number. ((string= type "coderef") (format (org-export-get-coderef-format path desc) - (org-export-resolve-coderef path info))) + ;; Resolve with RAW-PATH since PATH could be tainted + ;; with `org-latex--protect-text' call above. + (org-export-resolve-coderef raw-path info))) ;; External link with a description part. ((and path desc) (format "\\href{%s}{%s}" path desc)) ;; External link without a description part. diff --git a/lisp/org/ox-man.el b/lisp/org/ox-man.el index 36822ff9664..0e487d8966c 100644 --- a/lisp/org/ox-man.el +++ b/lisp/org/ox-man.el @@ -40,6 +40,8 @@ (require 'cl-lib) (require 'ox) +;;; Function Declarations + (defvar org-export-man-default-packages-alist) (defvar org-export-man-packages-alist) (defvar orgtbl-exp-regexp) @@ -599,24 +601,24 @@ CONTENTS is nil. INFO is a plist holding contextual information." ;;; Link -(defun org-man-link (link desc _info) +(defun org-man-link (link desc info) "Transcode a LINK object from Org to Man. DESC is the description part of the link, or the empty string. INFO is a plist holding contextual information. See `org-export-data'." (let* ((type (org-element-property :type link)) - (raw-path (org-element-property :path link)) + (raw-path (org-element-property :path link)) ;; Ensure DESC really exists, or set it to nil. (desc (and (not (string= desc "")) desc)) - (path (cond - ((member type '("http" "https" "ftp" "mailto")) - (concat type ":" raw-path)) - ((string= type "file") (org-export-file-uri raw-path)) - (t raw-path)))) + (path (pcase type + ((or "http" "https" "ftp" "mailto") + (concat type ":" raw-path)) + ("file" (org-export-file-uri raw-path)) + (_ raw-path)))) (cond ;; Link type is handled by a special function. - ((org-export-custom-protocol-maybe link desc 'man)) + ((org-export-custom-protocol-maybe link desc 'man info)) ;; External link with a description part. ((and path desc) (format "%s \\fBat\\fP \\fI%s\\fP" path desc)) ;; External link without a description part. @@ -1136,8 +1138,4 @@ Return PDF file name or an error if it couldn't be produced." (provide 'ox-man) -;; Local variables: -;; generated-autoload-file: "org-loaddefs.el" -;; End: - ;;; ox-man.el ends here diff --git a/lisp/org/ox-md.el b/lisp/org/ox-md.el index 91d5c0ba089..1d20c04f44d 100644 --- a/lisp/org/ox-md.el +++ b/lisp/org/ox-md.el @@ -85,13 +85,17 @@ The %s will be replaced by the footnote reference itself." (if a (org-md-export-to-markdown t s v) (org-open-file (org-md-export-to-markdown nil s v))))))) :translate-alist '((bold . org-md-bold) + (center-block . org-md--convert-to-html) (code . org-md-verbatim) + (drawer . org-md--identity) + (dynamic-block . org-md--identity) (example-block . org-md-example-block) (export-block . org-md-export-block) (fixed-width . org-md-example-block) (headline . org-md-headline) (horizontal-rule . org-md-horizontal-rule) (inline-src-block . org-md-verbatim) + (inlinetask . org-md--convert-to-html) (inner-template . org-md-inner-template) (italic . org-md-italic) (item . org-md-item) @@ -105,7 +109,9 @@ The %s will be replaced by the footnote reference itself." (property-drawer . org-md-property-drawer) (quote-block . org-md-quote-block) (section . org-md-section) + (special-block . org-md--convert-to-html) (src-block . org-md-example-block) + (table . org-md--convert-to-html) (template . org-md-template) (verbatim . org-md-verbatim)) :options-alist @@ -147,6 +153,145 @@ Assume BACKEND is `md'." ;; Return updated tree. tree) + +;;; Internal functions + +(defun org-md--headline-referred-p (headline info) + "Non-nil when HEADLINE is being referred to. +INFO is a plist used as a communication channel. Links and table +of contents can refer to headlines." + (unless (org-element-property :footnote-section-p headline) + (or + ;; Global table of contents includes HEADLINE. + (and (plist-get info :with-toc) + (memq headline + (org-export-collect-headlines info (plist-get info :with-toc)))) + ;; A local table of contents includes HEADLINE. + (cl-some + (lambda (h) + (let ((section (car (org-element-contents h)))) + (and + (eq 'section (org-element-type section)) + (org-element-map section 'keyword + (lambda (keyword) + (when (equal "TOC" (org-element-property :key keyword)) + (let ((case-fold-search t) + (value (org-element-property :value keyword))) + (and (string-match-p "\\" value) + (let ((n (and + (string-match "\\<[0-9]+\\>" value) + (string-to-number (match-string 0 value)))) + (local? (string-match-p "\\" value))) + (memq headline + (org-export-collect-headlines + info n (and local? keyword)))))))) + info t)))) + (org-element-lineage headline)) + ;; A link refers internally to HEADLINE. + (org-element-map (plist-get info :parse-tree) 'link + (lambda (link) + (eq headline + (pcase (org-element-property :type link) + ((or "custom-id" "id") (org-export-resolve-id-link link info)) + ("fuzzy" (org-export-resolve-fuzzy-link link info)) + (_ nil)))) + info t)))) + +(defun org-md--headline-title (style level title &optional anchor tags) + "Generate a headline title in the preferred Markdown headline style. +STYLE is the preferred style (`atx' or `setext'). LEVEL is the +header level. TITLE is the headline title. ANCHOR is the HTML +anchor tag for the section as a string. TAGS are the tags set on +the section." + (let ((anchor-lines (and anchor (concat anchor "\n\n")))) + ;; Use "Setext" style + (if (and (eq style 'setext) (< level 3)) + (let* ((underline-char (if (= level 1) ?= ?-)) + (underline (concat (make-string (length title) underline-char) + "\n"))) + (concat "\n" anchor-lines title tags "\n" underline "\n")) + ;; Use "Atx" style + (let ((level-mark (make-string level ?#))) + (concat "\n" anchor-lines level-mark " " title tags "\n\n"))))) + +(defun org-md--build-toc (info &optional n _keyword scope) + "Return a table of contents. + +INFO is a plist used as a communication channel. + +Optional argument N, when non-nil, is an integer specifying the +depth of the table. + +When optional argument SCOPE is non-nil, build a table of +contents according to the specified element." + (concat + (unless scope + (let ((style (plist-get info :md-headline-style)) + (title (org-html--translate "Table of Contents" info))) + (org-md--headline-title style 1 title nil))) + (mapconcat + (lambda (headline) + (let* ((indentation + (make-string + (* 4 (1- (org-export-get-relative-level headline info))) + ?\s)) + (bullet + (if (not (org-export-numbered-headline-p headline info)) "- " + (let ((prefix + (format "%d." (org-last (org-export-get-headline-number + headline info))))) + (concat prefix (make-string (max 1 (- 4 (length prefix))) + ?\s))))) + (title + (format "[%s](#%s)" + (org-export-data-with-backend + (org-export-get-alt-title headline info) + (org-export-toc-entry-backend 'md) + info) + (or (org-element-property :CUSTOM_ID headline) + (org-export-get-reference headline info)))) + (tags (and (plist-get info :with-tags) + (not (eq 'not-in-toc (plist-get info :with-tags))) + (org-make-tag-string + (org-export-get-tags headline info))))) + (concat indentation bullet title tags))) + (org-export-collect-headlines info n scope) "\n") + "\n")) + +(defun org-md--footnote-formatted (footnote info) + "Formats a single footnote entry FOOTNOTE. +FOOTNOTE is a cons cell of the form (number . definition). +INFO is a plist with contextual information." + (let* ((fn-num (car footnote)) + (fn-text (cdr footnote)) + (fn-format (plist-get info :md-footnote-format)) + (fn-anchor (format "fn.%d" fn-num)) + (fn-href (format " href=\"#fnr.%d\"" fn-num)) + (fn-link-to-ref (org-html--anchor fn-anchor fn-num fn-href info))) + (concat (format fn-format fn-link-to-ref) " " fn-text "\n"))) + +(defun org-md--footnote-section (info) + "Format the footnote section. +INFO is a plist used as a communication channel." + (let* ((fn-alist (org-export-collect-footnote-definitions info)) + (fn-alist (cl-loop for (n _type raw) in fn-alist collect + (cons n (org-trim (org-export-data raw info))))) + (headline-style (plist-get info :md-headline-style)) + (section-title (org-html--translate "Footnotes" info))) + (when fn-alist + (format (plist-get info :md-footnotes-section) + (org-md--headline-title headline-style 1 section-title) + (mapconcat (lambda (fn) (org-md--footnote-formatted fn info)) + fn-alist + "\n"))))) + +(defun org-md--convert-to-html (datum _contents info) + "Convert DATUM into raw HTML, including contents." + (org-export-data-with-backend datum 'html info)) + +(defun org-md--identity (_datum contents _info) + "Return CONTENTS only." + contents) ;;; Transcode Functions @@ -242,65 +387,6 @@ a communication channel." (concat (org-md--headline-title style level heading anchor tags) contents))))))) - -(defun org-md--headline-referred-p (headline info) - "Non-nil when HEADLINE is being referred to. -INFO is a plist used as a communication channel. Links and table -of contents can refer to headlines." - (unless (org-element-property :footnote-section-p headline) - (or - ;; Global table of contents includes HEADLINE. - (and (plist-get info :with-toc) - (memq headline - (org-export-collect-headlines info (plist-get info :with-toc)))) - ;; A local table of contents includes HEADLINE. - (cl-some - (lambda (h) - (let ((section (car (org-element-contents h)))) - (and - (eq 'section (org-element-type section)) - (org-element-map section 'keyword - (lambda (keyword) - (when (equal "TOC" (org-element-property :key keyword)) - (let ((case-fold-search t) - (value (org-element-property :value keyword))) - (and (string-match-p "\\" value) - (let ((n (and - (string-match "\\<[0-9]+\\>" value) - (string-to-number (match-string 0 value)))) - (local? (string-match-p "\\" value))) - (memq headline - (org-export-collect-headlines - info n (and local? keyword)))))))) - info t)))) - (org-element-lineage headline)) - ;; A link refers internally to HEADLINE. - (org-element-map (plist-get info :parse-tree) 'link - (lambda (link) - (eq headline - (pcase (org-element-property :type link) - ((or "custom-id" "id") (org-export-resolve-id-link link info)) - ("fuzzy" (org-export-resolve-fuzzy-link link info)) - (_ nil)))) - info t)))) - -(defun org-md--headline-title (style level title &optional anchor tags) - "Generate a headline title in the preferred Markdown headline style. -STYLE is the preferred style (`atx' or `setext'). LEVEL is the -header level. TITLE is the headline title. ANCHOR is the HTML -anchor tag for the section as a string. TAGS are the tags set on -the section." - (let ((anchor-lines (and anchor (concat anchor "\n\n")))) - ;; Use "Setext" style - (if (and (eq style 'setext) (< level 3)) - (let* ((underline-char (if (= level 1) ?= ?-)) - (underline (concat (make-string (length title) underline-char) - "\n"))) - (concat "\n" anchor-lines title tags "\n" underline "\n")) - ;; Use "Atx" style - (let ((level-mark (make-string level ?#))) - (concat "\n" anchor-lines level-mark " " title tags "\n\n"))))) - ;;;; Horizontal Rule (defun org-md-horizontal-rule (_horizontal-rule _contents _info) @@ -385,20 +471,28 @@ channel." ;;;; Link -(defun org-md-link (link contents info) - "Transcode LINE-BREAK object into Markdown format. -CONTENTS is the link's description. INFO is a plist used as -a communication channel." - (let ((link-org-files-as-md - (lambda (raw-path) - ;; Treat links to `file.org' as links to `file.md'. - (if (string= ".org" (downcase (file-name-extension raw-path "."))) - (concat (file-name-sans-extension raw-path) ".md") - raw-path))) - (type (org-element-property :type link))) +(defun org-md-link (link desc info) + "Transcode LINK object into Markdown format. +DESC is the description part of the link, or the empty string. +INFO is a plist holding contextual information. See +`org-export-data'." + (let* ((link-org-files-as-md + (lambda (raw-path) + ;; Treat links to `file.org' as links to `file.md'. + (if (string= ".org" (downcase (file-name-extension raw-path "."))) + (concat (file-name-sans-extension raw-path) ".md") + raw-path))) + (type (org-element-property :type link)) + (raw-path (org-element-property :path link)) + (path (cond + ((member type '("http" "https" "ftp" "mailto")) + (concat type ":" raw-path)) + ((string-equal type "file") + (org-export-file-uri (funcall link-org-files-as-md raw-path))) + (t raw-path)))) (cond ;; Link type is handled by a special function. - ((org-export-custom-protocol-maybe link contents 'md)) + ((org-export-custom-protocol-maybe link desc 'md info)) ((member type '("custom-id" "id" "fuzzy")) (let ((destination (if (string= type "fuzzy") (org-export-resolve-fuzzy-link link info) @@ -406,13 +500,13 @@ a communication channel." (pcase (org-element-type destination) (`plain-text ; External file. (let ((path (funcall link-org-files-as-md destination))) - (if (not contents) (format "<%s>" path) - (format "[%s](%s)" contents path)))) + (if (not desc) (format "<%s>" path) + (format "[%s](%s)" desc path)))) (`headline (format "[%s](#%s)" ;; Description. - (cond ((org-string-nw-p contents)) + (cond ((org-string-nw-p desc)) ((org-export-numbered-headline-p destination info) (mapconcat #'number-to-string (org-export-get-headline-number destination info) @@ -424,7 +518,7 @@ a communication channel." (org-export-get-reference destination info)))) (_ (let ((description - (or (org-string-nw-p contents) + (or (org-string-nw-p desc) (let ((number (org-export-get-ordinal destination info))) (cond ((not number) nil) @@ -435,31 +529,23 @@ a communication channel." description (org-export-get-reference destination info)))))))) ((org-export-inline-image-p link org-html-inline-image-rules) - (let ((path (let ((raw-path (org-element-property :path link))) - (cond ((not (equal "file" type)) (concat type ":" raw-path)) - ((not (file-name-absolute-p raw-path)) raw-path) - (t (expand-file-name raw-path))))) + (let ((path (cond ((not (string-equal type "file")) + (concat type ":" raw-path)) + ((not (file-name-absolute-p raw-path)) raw-path) + (t (expand-file-name raw-path)))) (caption (org-export-data (org-export-get-caption - (org-export-get-parent-element link)) info))) + (org-export-get-parent-element link)) + info))) (format "![img](%s)" (if (not (org-string-nw-p caption)) path (format "%s \"%s\"" path caption))))) ((string= type "coderef") - (let ((ref (org-element-property :path link))) - (format (org-export-get-coderef-format ref contents) - (org-export-resolve-coderef ref info)))) - ((equal type "radio") contents) - (t (let* ((raw-path (org-element-property :path link)) - (path - (cond - ((member type '("http" "https" "ftp" "mailto")) - (concat type ":" raw-path)) - ((string= type "file") - (org-export-file-uri (funcall link-org-files-as-md raw-path))) - (t raw-path)))) - (if (not contents) (format "<%s>" path) - (format "[%s](%s)" contents path))))))) + (format (org-export-get-coderef-format path desc) + (org-export-resolve-coderef path info))) + ((equal type "radio") desc) + (t (if (not desc) (format "<%s>" path) + (format "[%s](%s)" desc path)))))) ;;;; Node Property @@ -555,77 +641,6 @@ a communication channel." ;;;; Template -(defun org-md--build-toc (info &optional n _keyword scope) - "Return a table of contents. - -INFO is a plist used as a communication channel. - -Optional argument N, when non-nil, is an integer specifying the -depth of the table. - -When optional argument SCOPE is non-nil, build a table of -contents according to the specified element." - (concat - (unless scope - (let ((style (plist-get info :md-headline-style)) - (title (org-html--translate "Table of Contents" info))) - (org-md--headline-title style 1 title nil))) - (mapconcat - (lambda (headline) - (let* ((indentation - (make-string - (* 4 (1- (org-export-get-relative-level headline info))) - ?\s)) - (bullet - (if (not (org-export-numbered-headline-p headline info)) "- " - (let ((prefix - (format "%d." (org-last (org-export-get-headline-number - headline info))))) - (concat prefix (make-string (max 1 (- 4 (length prefix))) - ?\s))))) - (title - (format "[%s](#%s)" - (org-export-data-with-backend - (org-export-get-alt-title headline info) - (org-export-toc-entry-backend 'md) - info) - (or (org-element-property :CUSTOM_ID headline) - (org-export-get-reference headline info)))) - (tags (and (plist-get info :with-tags) - (not (eq 'not-in-toc (plist-get info :with-tags))) - (org-make-tag-string - (org-export-get-tags headline info))))) - (concat indentation bullet title tags))) - (org-export-collect-headlines info n scope) "\n") - "\n")) - -(defun org-md--footnote-formatted (footnote info) - "Formats a single footnote entry FOOTNOTE. -FOOTNOTE is a cons cell of the form (number . definition). -INFO is a plist with contextual information." - (let* ((fn-num (car footnote)) - (fn-text (cdr footnote)) - (fn-format (plist-get info :md-footnote-format)) - (fn-anchor (format "fn.%d" fn-num)) - (fn-href (format " href=\"#fnr.%d\"" fn-num)) - (fn-link-to-ref (org-html--anchor fn-anchor fn-num fn-href info))) - (concat (format fn-format fn-link-to-ref) " " fn-text "\n"))) - -(defun org-md--footnote-section (info) - "Format the footnote section. -INFO is a plist used as a communication channel." - (let* ((fn-alist (org-export-collect-footnote-definitions info)) - (fn-alist (cl-loop for (n _type raw) in fn-alist collect - (cons n (org-trim (org-export-data raw info))))) - (headline-style (plist-get info :md-headline-style)) - (section-title (org-html--translate "Footnotes" info))) - (when fn-alist - (format (plist-get info :md-footnotes-section) - (org-md--headline-title headline-style 1 section-title) - (mapconcat (lambda (fn) (org-md--footnote-formatted fn info)) - fn-alist - "\n"))))) - (defun org-md-inner-template (contents info) "Return body of document after converting it to Markdown syntax. CONTENTS is the transcoded contents string. INFO is a plist diff --git a/lisp/org/ox-odt.el b/lisp/org/ox-odt.el index 51cb42a49a5..3b90d03b1d7 100644 --- a/lisp/org/ox-odt.el +++ b/lisp/org/ox-odt.el @@ -96,7 +96,7 @@ (if a (org-odt-export-to-odt t s v) (org-open-file (org-odt-export-to-odt nil s v) 'system)))))) :options-alist - '((:odt-styles-file "ODT_STYLES_FILE" nil nil t) + '((:odt-styles-file "ODT_STYLES_FILE" nil org-odt-styles-file t) (:description "DESCRIPTION" nil nil newline) (:keywords "KEYWORDS" nil nil space) (:subtitle "SUBTITLE" nil nil parse) @@ -110,7 +110,6 @@ (:odt-inline-formula-rules nil nil org-odt-inline-formula-rules) (:odt-inline-image-rules nil nil org-odt-inline-image-rules) (:odt-pixels-per-inch nil nil org-odt-pixels-per-inch) - (:odt-styles-file nil nil org-odt-styles-file) (:odt-table-styles nil nil org-odt-table-styles) (:odt-use-date-fields nil nil org-odt-use-date-fields) ;; Redefine regular option. @@ -741,7 +740,7 @@ link's path." :value-type (regexp :tag "Path"))) (defcustom org-odt-inline-image-rules - '(("file" . "\\.\\(jpeg\\|jpg\\|png\\|gif\\|svg\\)\\'")) + `(("file" . ,(regexp-opt '(".jpeg" ".jpg" ".png" ".gif" ".svg")))) "Rules characterizing image files that can be inlined into ODT. A rule consists in an association whose key is the type of link @@ -940,7 +939,7 @@ See `org-odt--build-date-styles' for implementation details." (has-time-p (or (not timestamp) (org-timestamp-has-time-p timestamp))) (iso-date (let ((format (if has-time-p "%Y-%m-%dT%H:%M:%S" - "%Y-%m-%dT%H:%M:%S"))) + "%Y-%m-%d"))) (funcall format-timestamp timestamp format end)))) (if iso-date-p iso-date (let* ((style (if has-time-p "OrgDate2" "OrgDate1")) @@ -1383,6 +1382,8 @@ original parsed data. INFO is a plist holding export options." ;; create a manifest entry for styles.xml (org-odt-create-manifest-file-entry "text/xml" "styles.xml") + ;; Ensure we have write permissions to this file. + (set-file-modes (concat org-odt-zip-dir "styles.xml") #o600) ;; FIXME: Who is opening an empty styles.xml before this point? (with-current-buffer @@ -2199,16 +2200,15 @@ SHORT-CAPTION are strings." (defun org-odt--image-size (file info &optional user-width user-height scale dpi embed-as) (let* ((--pixels-to-cms - (function (lambda (pixels dpi) - (let ((cms-per-inch 2.54) - (inches (/ pixels dpi))) - (* cms-per-inch inches))))) + (lambda (pixels dpi) + (let ((cms-per-inch 2.54) + (inches (/ pixels dpi))) + (* cms-per-inch inches)))) (--size-in-cms - (function - (lambda (size-in-pixels dpi) - (and size-in-pixels - (cons (funcall --pixels-to-cms (car size-in-pixels) dpi) - (funcall --pixels-to-cms (cdr size-in-pixels) dpi)))))) + (lambda (size-in-pixels dpi) + (and size-in-pixels + (cons (funcall --pixels-to-cms (car size-in-pixels) dpi) + (funcall --pixels-to-cms (cdr size-in-pixels) dpi))))) (dpi (or dpi (plist-get info :odt-pixels-per-inch))) (anchor-type (or embed-as "paragraph")) (user-width (and (not scale) user-width)) @@ -2699,13 +2699,14 @@ INFO is a plist holding contextual information. See (path (cond ((member type '("http" "https" "ftp" "mailto")) (concat type ":" raw-path)) - ((string= type "file") (org-export-file-uri raw-path)) + ((string= type "file") + (org-export-file-uri raw-path)) (t raw-path))) ;; Convert & to & for correct XML representation (path (replace-regexp-in-string "&" "&" path))) (cond ;; Link type is handled by a special function. - ((org-export-custom-protocol-maybe link desc 'odt)) + ((org-export-custom-protocol-maybe link desc 'odt info)) ;; Image file. ((and (not desc) imagep) (org-odt-link--inline-image link info)) ;; Formula file. @@ -2946,7 +2947,7 @@ channel." (when scheduled (concat (format "%s" - "OrgScheduledKeyword" org-deadline-string) + "OrgScheduledKeyword" org-scheduled-string) (org-odt-timestamp scheduled contents info))))))) @@ -3728,7 +3729,8 @@ contextual information." (cache-dir (file-name-directory input-file)) (cache-subdir (concat (cl-case processing-type - ((dvipng imagemagick) "ltxpng/") + ((dvipng imagemagick) + org-preview-latex-image-directory) (mathml "ltxmathml/")) (file-name-sans-extension (file-name-nondirectory input-file)))) @@ -4239,7 +4241,7 @@ Return output file's name." `((?i . ,(shell-quote-argument in-file)) (?I . ,(browse-url-file-url in-file)) (?f . ,out-fmt) - (?o . ,out-file) + (?o . ,(shell-quote-argument out-file)) (?O . ,(browse-url-file-url out-file)) (?d . , (shell-quote-argument out-dir)) (?D . ,(browse-url-file-url out-dir)) diff --git a/lisp/org/ox-org.el b/lisp/org/ox-org.el index 97d8d0e92b9..740419e0e38 100644 --- a/lisp/org/ox-org.el +++ b/lisp/org/ox-org.el @@ -165,11 +165,11 @@ CONTENTS is nil. INFO is ignored." '("AUTHOR" "CREATOR" "DATE" "EMAIL" "OPTIONS" "TITLE")) (org-element-keyword-interpreter keyword nil)))) -(defun org-org-link (link contents _info) +(defun org-org-link (link contents info) "Transcode LINK object back into Org syntax. CONTENTS is the description of the link, as a string, or nil. INFO is a plist containing current export state." - (or (org-export-custom-protocol-maybe link contents 'org) + (or (org-export-custom-protocol-maybe link contents 'org info) (org-element-link-interpreter link contents))) (defun org-org-template (contents info) diff --git a/lisp/org/ox-publish.el b/lisp/org/ox-publish.el index a476796568c..7bb2fed6e18 100644 --- a/lisp/org/ox-publish.el +++ b/lisp/org/ox-publish.el @@ -659,8 +659,8 @@ If `:auto-sitemap' is set, publish the sitemap too. If (let ((plist (cdr project))) (let ((fun (org-publish-property :preparation-function project))) (cond - ((consp fun) (dolist (f fun) (funcall f plist))) - ((functionp fun) (funcall fun plist)))) + ((functionp fun) (funcall fun plist)) + ((consp fun) (dolist (f fun) (funcall f plist))))) ;; Each project uses its own cache file. (org-publish-initialize-cache (car project)) (when (org-publish-property :auto-sitemap project) @@ -685,8 +685,8 @@ If `:auto-sitemap' is set, publish the sitemap too. If (org-publish-file theindex project t))) (let ((fun (org-publish-property :completion-function project))) (cond - ((consp fun) (dolist (f fun) (funcall f plist))) - ((functionp fun) (funcall fun plist))))) + ((functionp fun) (funcall fun plist)) + ((consp fun) (dolist (f fun) (funcall f plist)))))) (org-publish-write-cache-file))) @@ -754,7 +754,8 @@ Default for SITEMAP-FILENAME is `sitemap.org'." (let* ((root (expand-file-name (file-name-as-directory (org-publish-property :base-directory project)))) - (sitemap-filename (concat root (or sitemap-filename "sitemap.org"))) + (sitemap-filename (expand-file-name (or sitemap-filename "sitemap.org") + root)) (title (or (org-publish-property :sitemap-title project) (concat "Sitemap for project " (car project)))) (style (or (org-publish-property :sitemap-style project) diff --git a/lisp/org/ox-texinfo.el b/lisp/org/ox-texinfo.el index 4265a85d1b2..ff4aa704b42 100644 --- a/lisp/org/ox-texinfo.el +++ b/lisp/org/ox-texinfo.el @@ -600,7 +600,8 @@ holding export options." "^@documentencoding \\(AUTO\\)$" coding (replace-regexp-in-string - "^@documentlanguage \\(AUTO\\)$" language header t nil 1) t nil 1))) + "^@documentlanguage \\(AUTO\\)$" language header t nil 1) + t nil 1))) ;; Additional header options set by #+TEXINFO_HEADER. (let ((texinfo-header (plist-get info :texinfo-header))) (and texinfo-header (org-element-normalize-string texinfo-header))) @@ -1049,13 +1050,15 @@ INFO is a plist holding contextual information. See (raw-path (org-element-property :path link)) ;; Ensure DESC really exists, or set it to nil. (desc (and (not (string= desc "")) desc)) - (path (cond - ((member type '("http" "https" "ftp")) - (concat type ":" raw-path)) - ((string= type "file") (org-export-file-uri raw-path)) - (t raw-path)))) + (path (org-texinfo--sanitize-content + (cond + ((member type '("http" "https" "ftp")) + (concat type ":" raw-path)) + ((string-equal type "file") + (org-export-file-uri raw-path)) + (t raw-path))))) (cond - ((org-export-custom-protocol-maybe link desc 'texinfo)) + ((org-export-custom-protocol-maybe link desc 'texinfo info)) ((org-export-inline-image-p link org-texinfo-inline-image-rules) (org-texinfo--inline-image link info)) ((equal type "radio") @@ -1069,8 +1072,7 @@ INFO is a plist holding contextual information. See (org-export-resolve-id-link link info)))) (pcase (org-element-type destination) (`nil - (format org-texinfo-link-with-unknown-path-format - (org-texinfo--sanitize-content path))) + (format org-texinfo-link-with-unknown-path-format path)) ;; Id link points to an external file. (`plain-text (if desc (format "@uref{file://%s,%s}" destination desc) @@ -1088,8 +1090,7 @@ INFO is a plist holding contextual information. See (_ (org-texinfo--@ref destination desc info))))) ((string= type "mailto") (format "@email{%s}" - (concat (org-texinfo--sanitize-content path) - (and desc (concat ", " desc))))) + (concat path (and desc (concat ", " desc))))) ;; External link with a description part. ((and path desc) (format "@uref{%s, %s}" path desc)) ;; External link without a description part. diff --git a/lisp/org/ox.el b/lisp/org/ox.el index 797efb90b79..6dd2cd4a089 100644 --- a/lisp/org/ox.el +++ b/lisp/org/ox.el @@ -172,12 +172,6 @@ All these properties should be back-end agnostic. Back-end specific properties are set through `org-export-define-backend'. Properties redefined there have precedence over these.") -(defconst org-export-special-keywords '("FILETAGS" "SETUPFILE" "OPTIONS") - "List of in-buffer keywords that require special treatment. -These keywords are not directly associated to a property. The -way they are handled must be hard-coded into -`org-export--get-inbuffer-options' function.") - (defconst org-export-filters-alist '((:filter-body . org-export-filter-body-functions) (:filter-bold . org-export-filter-bold-functions) @@ -1474,104 +1468,57 @@ Assume buffer is in Org mode. Narrowing, if any, is ignored." ;; Priority is given to back-end specific options. (org-export-get-all-options backend) org-export-options-alist)) - (regexp (format "^[ \t]*#\\+%s:" - (regexp-opt (nconc (delq nil (mapcar #'cadr options)) - org-export-special-keywords)))) plist to-parse) - (letrec ((find-properties - (lambda (keyword) - ;; Return all properties associated to KEYWORD. - (let (properties) - (dolist (option options properties) - (when (equal (nth 1 option) keyword) - (cl-pushnew (car option) properties)))))) - (get-options - (lambda (&optional files) - ;; Recursively read keywords in buffer. FILES is - ;; a list of files read so far. PLIST is the current - ;; property list obtained. - (org-with-wide-buffer - (goto-char (point-min)) - (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)) - (val (org-element-property :value element))) - (cond - ;; Options in `org-export-special-keywords'. - ((equal key "SETUPFILE") - (let* ((uri (org-strip-quotes (org-trim val))) - (uri-is-url (org-file-url-p uri)) - (uri (if uri-is-url - uri - (expand-file-name uri)))) - ;; Avoid circular dependencies. - (unless (member uri files) - (with-temp-buffer - (unless uri-is-url - (setq default-directory - (file-name-directory uri))) - (insert (org-file-contents uri 'noerror)) - (let ((org-inhibit-startup t)) (org-mode)) - (funcall get-options (cons uri files)))))) - ((equal key "OPTIONS") - (setq plist - (org-combine-plists - plist - (org-export--parse-option-keyword - val backend)))) - ((equal key "FILETAGS") - (setq plist - (org-combine-plists - plist - (list :filetags - (org-uniquify - (append - (org-split-string val ":") - (plist-get plist :filetags))))))) - (t - ;; Options in `org-export-options-alist'. - (dolist (property (funcall find-properties key)) - (setq - plist - (plist-put - plist property - ;; Handle value depending on specified - ;; BEHAVIOR. - (cl-case (nth 4 (assq property options)) - (parse - (unless (memq property to-parse) - (push property to-parse)) - ;; Even if `parse' implies `space' - ;; behavior, we separate line with - ;; "\n" so as to preserve - ;; line-breaks. However, empty - ;; lines are forbidden since `parse' - ;; doesn't allow more than one - ;; paragraph. - (let ((old (plist-get plist property))) - (cond ((not (org-string-nw-p val)) old) - (old (concat old "\n" val)) - (t val)))) - (space - (if (not (plist-get plist property)) - (org-trim val) - (concat (plist-get plist property) - " " - (org-trim val)))) - (newline - (org-trim - (concat (plist-get plist property) - "\n" - (org-trim val)))) - (split `(,@(plist-get plist property) - ,@(split-string val))) - ((t) val) - (otherwise - (if (not (plist-member plist property)) val - (plist-get plist property))))))))))))))))) + (let ((find-properties + (lambda (keyword) + ;; Return all properties associated to KEYWORD. + (let (properties) + (dolist (option options properties) + (when (equal (nth 1 option) keyword) + (cl-pushnew (car option) properties))))))) ;; Read options in the current buffer and return value. - (funcall get-options (and buffer-file-name (list buffer-file-name))) + (dolist (entry (org-collect-keywords + (nconc (delq nil (mapcar #'cadr options)) + '("FILETAGS" "OPTIONS")))) + (pcase entry + (`("OPTIONS" . ,values) + (setq plist + (apply #'org-combine-plists + plist + (mapcar (lambda (v) + (org-export--parse-option-keyword v backend)) + values)))) + (`("FILETAGS" . ,values) + (setq plist + (plist-put plist + :filetags + (org-uniquify + (cl-mapcan (lambda (v) (org-split-string v ":")) + values))))) + (`(,keyword . ,values) + (dolist (property (funcall find-properties keyword)) + (setq plist + (plist-put + plist property + ;; Handle value depending on specified BEHAVIOR. + (cl-case (nth 4 (assq property options)) + (parse + (unless (memq property to-parse) + (push property to-parse)) + ;; Even if `parse' implies `space' behavior, we + ;; separate line with "\n" so as to preserve + ;; line-breaks. + (mapconcat #'identity values "\n")) + (space + (mapconcat #'identity values " ")) + (newline + (mapconcat #'identity values "\n")) + (split + (cl-mapcan (lambda (v) (split-string v)) values)) + ((t) + (org-last values)) + (otherwise + (car values))))))))) ;; Parse properties in TO-PARSE. Remove newline characters not ;; involved in line breaks to simulate `space' behavior. ;; Finally return options. @@ -1633,44 +1580,10 @@ process." Also look for BIND keywords in setup files. The return value is an alist where associations are (VARIABLE-NAME VALUE)." (when org-export-allow-bind-keywords - (letrec ((collect-bind - (lambda (files alist) - ;; Return an alist between variable names and their - ;; value. FILES is a list of setup files names read - ;; so far, used to avoid circular dependencies. ALIST - ;; is the alist collected so far. - (let ((case-fold-search t)) - (org-with-wide-buffer - (goto-char (point-min)) - (while (re-search-forward - "^[ \t]*#\\+\\(BIND\\|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) - "BIND") - (push (read (format "(%s)" val)) alist) - ;; Enter setup file. - (let* ((uri (org-strip-quotes val)) - (uri-is-url (org-file-url-p uri)) - (uri (if uri-is-url - uri - (expand-file-name uri)))) - ;; Avoid circular dependencies. - (unless (member uri files) - (with-temp-buffer - (unless uri-is-url - (setq default-directory - (file-name-directory uri))) - (let ((org-inhibit-startup t)) (org-mode)) - (insert (org-file-contents uri 'noerror)) - (setq alist - (funcall collect-bind - (cons uri files) - alist)))))))))) - alist))))) - ;; Return value in appropriate order of appearance. - (nreverse (funcall collect-bind nil nil))))) + (pcase (org-collect-keywords '("BIND")) + (`(("BIND" . ,values)) + (mapcar (lambda (v) (read (format "(%s)" v))) + values))))) ;; defsubst org-export-get-parent must be defined before first use, ;; was originally defined in the topology section @@ -3461,15 +3374,16 @@ Move point after the link." (goto-char (org-element-property :end link)) (let ((new-path (file-relative-name (expand-file-name path file-dir) includer-dir)) - (new-link (org-element-copy link)) - (contents (and (org-element-property :contents-begin link) - (buffer-substring - (org-element-property :contents-begin link) - (org-element-property :contents-end link))))) + (new-link (org-element-copy link))) (org-element-put-property new-link :path new-path) + (when (org-element-property :contents-begin link) + (org-element-adopt-elements new-link + (buffer-substring + (org-element-property :contents-begin link) + (org-element-property :contents-end link)))) (delete-region (org-element-property :begin link) (org-element-property :end link)) - (insert (org-element-link-interpreter new-link contents)))))) + (insert (org-element-interpret-data new-link)))))) (defun org-export--prepare-file-contents (file &optional lines ind minlevel id footnotes includer) @@ -4184,8 +4098,8 @@ meant to be translated with `org-export-data' or alike." (org-define-error 'org-link-broken "Unable to resolve link; aborting") -(defun org-export-custom-protocol-maybe (link desc backend) - "Try exporting LINK with a dedicated function. +(defun org-export-custom-protocol-maybe (link desc backend &optional info) + "Try exporting LINK object with a dedicated function. DESC is its description, as a string, or nil. BACKEND is the back-end used for export, as a symbol. @@ -4196,14 +4110,20 @@ A custom protocol has precedence over regular back-end export. The function ignores links with an implicit type (e.g., \"custom-id\")." (let ((type (org-element-property :type link))) - (unless (or (member type '("coderef" "custom-id" "fuzzy" "radio")) + (unless (or (member type '("coderef" "custom-id" "fuzzy" "radio" nil)) (not backend)) - (let ((protocol (org-link-get-parameter type :export))) + (let ((protocol (org-link-get-parameter type :export)) + (path (org-element-property :path link))) (and (functionp protocol) - (funcall protocol - (org-element-property :path link) - desc - backend)))))) + (condition-case nil + (funcall protocol path desc backend info) + ;; XXX: The function used (< Org 9.4) to accept only + ;; three mandatory arguments. Type-specific `:export' + ;; functions in the wild may not handle current + ;; signature. Provide backward compatibility support + ;; for them. + (wrong-number-of-arguments + (funcall protocol path desc backend)))))))) (defun org-export-get-coderef-format (path desc) "Return format string for code reference link. @@ -4332,7 +4252,7 @@ ignores white spaces and statistics cookies, if applicable." (`headline (let ((title (split-string (replace-regexp-in-string - "\\[[0-9]*\\(?:%\\|/[0-9]*\\)\\]" "" + "\\[[0-9]*\\(?:%\\|/[0-9]*\\)\\]" " " (org-element-property :raw-value datum))))) (delq nil (list @@ -4805,9 +4725,6 @@ code." ;; `org-export-table-row-is-special-p' are predicates used to look for ;; meta-information about the table structure. ;; -;; `org-table-has-header-p' tells when the rows before the first rule -;; should be considered as table's header. -;; ;; `org-export-table-cell-width', `org-export-table-cell-alignment' ;; and `org-export-table-cell-borders' extract information from ;; a table-cell element. @@ -5243,7 +5160,8 @@ rows (resp. columns)." (lambda (row) (when (eq (org-element-property :type row) 'standard) (cl-incf rows) - (unless first-row (setq first-row row)))) info) + (unless first-row (setq first-row row)))) + info) ;; Set number of columns. (org-element-map first-row 'table-cell (lambda (_) (cl-incf columns)) info) ;; Return value. @@ -5459,7 +5377,7 @@ transcoding it." (apostrophe :utf-8 "’" :html "’")) ("da" ;; one may use: »...«, "...", ›...‹, or '...'. - ;; http://sproget.dk/raad-og-regler/retskrivningsregler/retskrivningsregler/a7-40-60/a7-58-anforselstegn/ + ;; https://sproget.dk/raad-og-regler/retskrivningsregler/retskrivningsregler/a7-40-60/a7-58-anforselstegn/ ;; LaTeX quotes require Babel! (primary-opening :utf-8 "»" :html "»" :latex ">>" :texinfo "@guillemetright{}") @@ -5552,8 +5470,19 @@ transcoding it." (secondary-opening :utf-8 "‘" :html "‘" :latex "`" :texinfo "`") (secondary-closing :utf-8 "’" :html "’" :latex "'" :texinfo "'") (apostrophe :utf-8 "’" :html "’")) + ("ro" + (primary-opening + :utf-8 "„" :html "„" :latex "\"`" :texinfo "@quotedblbase{}") + (primary-closing :utf-8 "”" :html "”" :latex "''" :texinfo "''") + (secondary-opening + :utf-8 "«" :html "«" :latex "\\guillemotleft{}" + :texinfo "@guillemetleft{}") + (secondary-closing + :utf-8 "»" :html "»" :latex "\\guillemotright{}" + :texinfo "@guillemetright{}") + (apostrophe :utf-8 "’" :html "’")) ("ru" - ;; http://ru.wikipedia.org/wiki/%D0%9A%D0%B0%D0%B2%D1%8B%D1%87%D0%BA%D0%B8#.D0.9A.D0.B0.D0.B2.D1.8B.D1.87.D0.BA.D0.B8.2C_.D0.B8.D1.81.D0.BF.D0.BE.D0.BB.D1.8C.D0.B7.D1.83.D0.B5.D0.BC.D1.8B.D0.B5_.D0.B2_.D1.80.D1.83.D1.81.D1.81.D0.BA.D0.BE.D0.BC_.D1.8F.D0.B7.D1.8B.D0.BA.D0.B5 + ;; https://ru.wikipedia.org/wiki/%D0%9A%D0%B0%D0%B2%D1%8B%D1%87%D0%BA%D0%B8#.D0.9A.D0.B0.D0.B2.D1.8B.D1.87.D0.BA.D0.B8.2C_.D0.B8.D1.81.D0.BF.D0.BE.D0.BB.D1.8C.D0.B7.D1.83.D0.B5.D0.BC.D1.8B.D0.B5_.D0.B2_.D1.80.D1.83.D1.81.D1.81.D0.BA.D0.BE.D0.BC_.D1.8F.D0.B7.D1.8B.D0.BA.D0.B5 ;; http://www.artlebedev.ru/kovodstvo/sections/104/ (primary-opening :utf-8 "«" :html "«" :latex "{}<<" :texinfo "@guillemetleft{}") @@ -5812,6 +5741,7 @@ them." ("nn" :default "Forfattar") ("pl" :default "Autor") ("pt_BR" :default "Autor") + ("ro" :default "Autor") ("ru" :html "Автор" :utf-8 "Автор") ("sl" :default "Avtor") ("sv" :html "Författare") @@ -5829,6 +5759,7 @@ them." ("nl" :default "Vervolg van vorige pagina") ("pt" :default "Continuação da página anterior") ("pt_BR" :html "Continuação da página anterior" :ascii "Continuacao da pagina anterior" :default "Continuação da página anterior") + ("ro" :default "Continuare de pe pagina precedentă") ("ru" :html "(Продолжение)" :utf-8 "(Продолжение)") ("sl" :default "Nadaljevanje s prejšnje strani")) @@ -5843,12 +5774,15 @@ them." ("nl" :default "Vervolg op volgende pagina") ("pt" :default "Continua na página seguinte") ("pt_BR" :html "Continua na próxima página" :ascii "Continua na proxima pagina" :default "Continua na próxima página") + ("ro" :default "Continuare pe pagina următoare") ("ru" :html "(Продолжение следует)" :utf-8 "(Продолжение следует)") ("sl" :default "Nadaljevanje na naslednji strani")) ("Created" ("cs" :default "Vytvořeno") + ("nl" :default "Gemaakt op") ;; must be followed by a date or date+time ("pt_BR" :default "Criado em") + ("ro" :default "Creat") ("sl" :default "Ustvarjeno")) ("Date" ("ar" :default "بتاريخ") @@ -5869,6 +5803,7 @@ them." ("nb" :default "Dato") ("nn" :default "Dato") ("pl" :default "Data") + ("ro" :default "Data") ("pt_BR" :default "Data") ("ru" :html "Дата" :utf-8 "Дата") ("sl" :default "Datum") @@ -5886,10 +5821,12 @@ them." ("fr" :ascii "Equation" :default "Équation") ("is" :default "Jafna") ("ja" :default "方程式") + ("nl" :default "Vergelijking") ("no" :default "Ligning") ("nb" :default "Ligning") ("nn" :default "Likning") ("pt_BR" :html "Equação" :default "Equação" :ascii "Equacao") + ("ro" :default "Ecuația") ("ru" :html "Уравнение" :utf-8 "Уравнение") ("sl" :default "Enačba") @@ -5905,10 +5842,12 @@ them." ("is" :default "Mynd") ("it" :default "Figura") ("ja" :default "図" :html "図") + ("nl" :default "Figuur") ("no" :default "Illustrasjon") ("nb" :default "Illustrasjon") ("nn" :default "Illustrasjon") ("pt_BR" :default "Figura") + ("ro" :default "Imaginea") ("ru" :html "Рисунок" :utf-8 "Рисунок") ("sv" :default "Illustration") ("zh-CN" :html "图" :utf-8 "图")) @@ -5923,10 +5862,12 @@ them." ("is" :default "Mynd %d") ("it" :default "Figura %d:") ("ja" :default "図%d: " :html "図%d: ") + ("nl" :default "Figuur %d:" :html "Figuur %d:") ("no" :default "Illustrasjon %d") ("nb" :default "Illustrasjon %d") ("nn" :default "Illustrasjon %d") ("pt_BR" :default "Figura %d:") + ("ro" :default "Imaginea %d:") ("ru" :html "Рис. %d.:" :utf-8 "Рис. %d.:") ("sl" :default "Slika %d") ("sv" :default "Illustration %d") @@ -5952,6 +5893,7 @@ them." ("nn" :default "Fotnotar") ("pl" :default "Przypis") ("pt_BR" :html "Notas de Rodapé" :default "Notas de Rodapé" :ascii "Notas de Rodape") + ("ro" :default "Note de subsol") ("ru" :html "Сноски" :utf-8 "Сноски") ("sl" :default "Opombe") ("sv" :default "Fotnoter") @@ -5968,6 +5910,7 @@ them." ("et" :default "Loendite nimekiri") ("fr" :default "Liste des programmes") ("ja" :default "ソースコード目次") + ("nl" :default "Lijst van programma's") ("no" :default "Dataprogrammer") ("nb" :default "Dataprogrammer") ("pt_BR" :html "Índice de Listagens" :default "Índice de Listagens" :ascii "Indice de Listagens") @@ -5986,10 +5929,12 @@ them." ("is" :default "Töfluskrá" :html "Töfluskrá") ("it" :default "Indice delle tabelle") ("ja" :default "表目次") + ("nl" :default "Lijst van tabellen") ("no" :default "Tabeller") ("nb" :default "Tabeller") ("nn" :default "Tabeller") ("pt_BR" :html "Índice de Tabelas" :default "Índice de Tabelas" :ascii "Indice de Tabelas") + ("ro" :default "Tabele") ("ru" :html "Список таблиц" :utf-8 "Список таблиц") ("sl" :default "Seznam tabel") @@ -6005,9 +5950,11 @@ them." ("fr" :default "Programme" :html "Programme") ("it" :default "Listato") ("ja" :default "ソースコード") + ("nl" :default "Programma") ("no" :default "Dataprogram") ("nb" :default "Dataprogram") ("pt_BR" :default "Listagem") + ("ro" :default "Lista") ("ru" :html "Распечатка" :utf-8 "Распечатка") ("sl" :default "Izpis programa") @@ -6022,8 +5969,10 @@ them." ("fr" :default "Programme %d :" :html "Programme %d :") ("it" :default "Listato %d :") ("ja" :default "ソースコード%d:") + ("nl" :default "Programma %d:" :html "Programma %d:") ("no" :default "Dataprogram %d") ("nb" :default "Dataprogram %d") + ("ro" :default "Lista %d") ("pt_BR" :default "Listagem %d:") ("ru" :html "Распечатка %d.:" :utf-8 "Распечатка %d.:") @@ -6036,20 +5985,28 @@ them." ("es" :default "Referencias") ("fr" :ascii "References" :default "Références") ("it" :default "Riferimenti") + ("nl" :default "Bronverwijzingen") ("pt_BR" :html "Referências" :default "Referências" :ascii "Referencias") + ("ro" :default "Bibliografie") ("sl" :default "Reference")) ("See figure %s" ("cs" :default "Viz obrázek %s") ("fr" :default "cf. figure %s" :html "cf. figure %s" :latex "cf.~figure~%s") ("it" :default "Vedi figura %s") + ("nl" :default "Zie figuur %s" + :html "Zie figuur %s" :latex "Zie figuur~%s") ("pt_BR" :default "Veja a figura %s") + ("ro" :default "Vezi figura %s") ("sl" :default "Glej sliko %s")) ("See listing %s" ("cs" :default "Viz program %s") ("fr" :default "cf. programme %s" :html "cf. programme %s" :latex "cf.~programme~%s") + ("nl" :default "Zie programma %s" + :html "Zie programma %s" :latex "Zie programma~%s") ("pt_BR" :default "Veja a listagem %s") + ("ro" :default "Vezi tabelul %s") ("sl" :default "Glej izpis programa %s")) ("See section %s" ("ar" :default "انظر قسم %s") @@ -6061,8 +6018,11 @@ them." ("fr" :default "cf. section %s") ("it" :default "Vedi sezione %s") ("ja" :default "セクション %s を参照") + ("nl" :default "Zie sectie %s" + :html "Zie sectie %s" :latex "Zie sectie~%s") ("pt_BR" :html "Veja a seção %s" :default "Veja a seção %s" :ascii "Veja a secao %s") + ("ro" :default "Vezi secțiunea %s") ("ru" :html "См. раздел %s" :utf-8 "См. раздел %s") ("sl" :default "Glej poglavje %d") @@ -6072,7 +6032,10 @@ them." ("fr" :default "cf. tableau %s" :html "cf. tableau %s" :latex "cf.~tableau~%s") ("it" :default "Vedi tabella %s") + ("nl" :default "Zie tabel %s" + :html "Zie tabel %s" :latex "Zie tabel~%s") ("pt_BR" :default "Veja a tabela %s") + ("ro" :default "Vezi tabelul %s") ("sl" :default "Glej tabelo %s")) ("Table" ("ar" :default "جدول") @@ -6084,7 +6047,9 @@ them." ("is" :default "Tafla") ("it" :default "Tabella") ("ja" :default "表" :html "表") + ("nl" :default "Tabel") ("pt_BR" :default "Tabela") + ("ro" :default "Tabel") ("ru" :html "Таблица" :utf-8 "Таблица") ("zh-CN" :html "表" :utf-8 "表")) @@ -6099,10 +6064,12 @@ them." ("is" :default "Tafla %d") ("it" :default "Tabella %d:") ("ja" :default "表%d:" :html "表%d:") + ("nl" :default "Tabel %d:" :html "Tabel %d:") ("no" :default "Tabell %d") ("nb" :default "Tabell %d") ("nn" :default "Tabell %d") ("pt_BR" :default "Tabela %d:") + ("ro" :default "Tabel %d") ("ru" :html "Таблица %d.:" :utf-8 "Таблица %d.:") ("sl" :default "Tabela %d") @@ -6129,6 +6096,7 @@ them." ("nn" :default "Innhald") ("pl" :html "Spis treści") ("pt_BR" :html "Índice" :utf8 "Índice" :ascii "Indice") + ("ro" :default "Cuprins") ("ru" :html "Содержание" :utf-8 "Содержание") ("sl" :default "Kazalo") @@ -6145,7 +6113,9 @@ them." ("fr" :ascii "Destination inconnue" :default "Référence inconnue") ("it" :default "Riferimento sconosciuto") ("ja" :default "不明な参照先") + ("nl" :default "Onbekende verwijzing") ("pt_BR" :html "Referência desconhecida" :default "Referência desconhecida" :ascii "Referencia desconhecida") + ("ro" :default "Referință necunoscută") ("ru" :html "Неизвестная ссылка" :utf-8 "Неизвестная ссылка") ("sl" :default "Neznana referenca") @@ -6877,10 +6847,12 @@ back to standard interface." (with-current-buffer "*Org Export Dispatcher*" ;; Refresh help. Maintain display continuity by re-visiting ;; previous window position. - (let ((pos (window-start))) + (let ((pt (point)) + (wstart (window-start))) (erase-buffer) (insert help) - (set-window-start nil pos))) + (goto-char pt) + (set-window-start nil wstart))) (org-fit-window-to-buffer) (org-export--dispatch-action standard-prompt allowed-keys entries options first-key expertp)))) @@ -6903,24 +6875,10 @@ options as CDR." ;; C-p, SPC, DEL). (while (and (setq key (read-char-exclusive prompt)) (not expertp) - (memq key '(14 16 ?\s ?\d))) - (cl-case key - (14 (if (not (pos-visible-in-window-p (point-max))) - (ignore-errors (scroll-up 1)) - (message "End of buffer") - (sit-for 1))) - (16 (if (not (pos-visible-in-window-p (point-min))) - (ignore-errors (scroll-down 1)) - (message "Beginning of buffer") - (sit-for 1))) - (?\s (if (not (pos-visible-in-window-p (point-max))) - (scroll-up nil) - (message "End of buffer") - (sit-for 1))) - (?\d (if (not (pos-visible-in-window-p (point-min))) - (scroll-down nil) - (message "Beginning of buffer") - (sit-for 1))))) + ;; FIXME: Don't use C-v (22) here, as it is used as a + ;; modifier key in the export dispatch. + (memq key '(14 16 ?\s ?\d 134217846))) + (org-scroll key t)) (cond ;; Ignore undefined associations. ((not (memq key allowed-keys)) @@ -6929,7 +6887,7 @@ options as CDR." (org-export--dispatch-ui options first-key expertp)) ;; q key at first level aborts export. At second level, cancel ;; first key instead. - ((eq key ?q) (if (not first-key) (error "Export aborted") + ((eq key ?q) (if (not first-key) (user-error "Export aborted") (org-export--dispatch-ui options nil expertp))) ;; Help key: Switch back to standard interface if expert UI was ;; active. -- cgit v1.2.1